• Problem D. 送快递 【最短路 | 反悔贪心(堆维护)】


    Flash 是 A 城里的一位快递小哥,A 城中共有 n + 1 家住户,门牌号依次为 0, 1, 2, · · · , n,它们通过 m 条双向道路相连。Flash 位于的快递站门牌号为 0,保证可以到达 A 城中的任意一家住户。 Flash 今天(第一天)接到了一个派送任务,需要向每一家住户派送一个神秘快递,但是 Flash 的能力 有限,每天只能派送一件快递且只能从快递站出发。每一件快递都标明了一个截止日期,Flash 需要在 第 i 个快递的截止日期 ti 前将该快递送至门牌号为 i 的住户手中。例如,若在第 3 天,某个未被派送快 递的截止日期为 3,当天派送它则可以成功送达,但如果同时有两个截止日期为 3 的未被派送的快递, 那么 Flash 只能选择一个进行派送,另外一个必定超时。 对于每一件快递而言,其派送费等于最短派送路径的长度。但是,若某个快递超时,Flash 将会被处以 相应派送费金额的罚款,并且不会得到任何派送费! 收益指总派送费减总罚款,请你帮忙计算出 Flash 此次任务的最大收益为多少?

    操作对象是一个结构体,分别记录截止时间和最短距离。方法是贪心,问题是怎么贪心。

    首先想正面贪心,截止时间小的在前面,选出同一时间里面最短距离最大的,但是明显有bug。

    法一:反向贪心,不难发现每一天只能对应送一个快递,用vis数组记录当前天有没有快递要送。一共送n天。这样想就需要按照最短距离从大到小排序,然后从截止时间开始从后往前遍历即可。

    1. #include
    2. typedef long long ll;
    3. using namespace std;
    4. const ll N = 1e5 + 10, M = 2e5 + 10;
    5. ll h[N], w[M], ne[M], e[M], idx;
    6. struct Node
    7. {
    8. ll d, t;
    9. bool operator < (const Node & x) const {
    10. if(d == x.d) return t > x.t;
    11. else return d > x.d;
    12. }
    13. }ed[N];
    14. void add(ll a, ll b, ll c)
    15. {
    16. e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++ ;
    17. }
    18. ll dis[N];
    19. bool st[N], vis[N];
    20. ll n,m;
    21. void spfa()
    22. {
    23. memset(dis, 0x3f, sizeof dis);
    24. queue q;
    25. q.push(0);
    26. dis[0] = 0;
    27. st[0] = 1;
    28. while(q.size())
    29. {
    30. ll t = q.front();
    31. q.pop();
    32. st[t] = 0;
    33. for (ll i = h[t]; ~i; i = ne[i])
    34. {
    35. ll j = e[i];
    36. if(dis[j] > dis[t] + w[i]) {
    37. dis[j] = dis[t] + w[i];
    38. if(!st[j]) {
    39. st[j] = 1;
    40. q.push(j);
    41. }
    42. }
    43. }
    44. }
    45. }
    46. int main(){
    47. std::ios::sync_with_stdio(false);
    48. //std::cin.tie(nullptr);
    49. memset(h, -1, sizeof h);
    50. cin >> n >> m;
    51. while(m -- ) {
    52. ll u, v, w;
    53. cin >> u >> v >> w;
    54. add(u, v, w), add(v, u, w);
    55. }
    56. spfa();
    57. for (ll i = 1, x; i <= n; i ++ ) {
    58. cin >> x;
    59. ed[i] = {dis[i], x};
    60. }
    61. ll ans = 0;
    62. sort(ed + 1, ed + 1 + n);
    63. for (ll i = 1; i <= n; i ++ ) {
    64. bool ok = false;
    65. for (ll j = ed[i].t; j >= 1; j -- ) {
    66. if(!vis[j])
    67. {
    68. vis[j] = 1;
    69. ans += ed[i].d;
    70. ok = true;
    71. break;
    72. }
    73. }
    74. if(!ok) ans -= ed[i].d;
    75. }
    76. cout << ans << '\n';
    77. return 0;
    78. }

    法二:反悔贪心

    用堆维护,堆用优先队列写。

    道理非常简单。首先小根堆顶是最小值,每次拿堆顶和当前的值比较,如果不满足条件pop,所以我们最终留在队列里的是较大值,所以用小根堆。

    while (qu.size() >= d[i].t && qu.top() < d[i].dis) qu.pop(); qu.size() >= d[i].t是因为如果t=4,那么你最多选出来最大值的个数就只能是四个,因为截止日期就是4,只有所有小于等于4的能被维护,一天送一个一共送4天。比如 1 1 3 4可以 1 1 2 3 4不行,这样必定使得第五天的4超时。这其实和上面的解法一个思路。

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. using namespace std;
    10. int n, m, u, v, w, vis[100005];
    11. vector < pair < int, int> > edg[100005];
    12. long long dis[100005], a[100005], maxd[100005], ans;
    13. priority_queue< long long, vector <long long >, greater< long long>> qu;
    14. struct DATE {
    15. long long t, dis;
    16. inline bool operator < (const DATE& d2) const {
    17. return t < d2.t;
    18. }
    19. } d[100005];
    20. void SPFA() {
    21. for (int i = 1; i <= n; i++) dis[i] = 1e10;
    22. queue < int > qu;
    23. qu.push(0);
    24. while (!qu.empty()) {
    25. u = qu.front(), qu.pop();
    26. vis[u] = 0;
    27. for (int i = 0; i < edg[u].size(); i++) {
    28. v = edg[u][i].first;
    29. if (dis[v] > dis[u] + edg[u][i].second) {
    30. dis[v] = dis[u] + edg[u][i].second;
    31. if (!vis[v])qu.push(v), vis[v] = 1;
    32. }
    33. }
    34. }
    35. return;
    36. }
    37. int main() {
    38. scanf("%d%d", &n, &m);
    39. for (int i = 1; i <= m; i++) {
    40. scanf("%d%d%d", &u, &v, &w);
    41. edg[u].push_back({ v, w });
    42. edg[v].push_back({ u, w });
    43. }
    44. SPFA();
    45. for (int i = 1; i <= n; i++) {
    46. scanf("%d", &d[i].t);
    47. d[i].dis = dis[i];
    48. ans += dis[i];
    49. }
    50. sort(d + 1, d + n + 1);
    51. qu.push(d[1].dis);
    52. for (int i = 2; i <= n; i++) {
    53. while (qu.size() >= d[i].t && qu.top() < d[i].dis) qu.pop();
    54. if (qu.size() < d[i].t) qu.push(d[i].dis);
    55. }
    56. while (!qu.empty()) ans -= qu.top() * 2, qu.pop();
    57. printf("%lld\n", -ans);
    58. return 0;
    59. }

    略:

    道理非常简单。首先小根堆顶是最小值,每次拿堆顶和当前的值比较,如果不满足条件pop,所以我们最终留在队列里的是较大值,所以用小根堆。

    while (qu.size() >= d[i].t && qu.top() < d[i].dis) qu.pop(); qu.size() >= d[i].t是因为如果t=4,那么你最多选出来最大值的个数就只能是四个,因为截止日期就是4,只有所有小于等于4的能被维护,一天送一个一共送4天。比如 1 1 3 4可以 1 1 2 3 4不行,这样必定使得第五天的4超时。这其实和上面的解法一个思路。

    道理非常简单。首先小根堆顶是最小值,每次拿堆顶和当前的值比较,如果不满足条件pop,所以我们最终留在队列里的是较大值,所以用小根堆。

    while (qu.size() >= d[i].t && qu.top() < d[i].dis) qu.pop(); qu.size() >= d[i].t是因为如果t=4,那么你最多选出来最大值的个数就只能是四个,因为截止日期就是4,只有所有小于等于4的能被维护,一天送一个一共送4天。比如 1 1 3 4可以 1 1 2 3 4不行,这样必定使得第五天的4超时。这其实和上面的解法一个思路。

  • 相关阅读:
    支付宝内部打开h5页面报错
    34.【C/C++ string大汇总,不看定后悔】
    LVS+keepalived
    【面试题】JS使用parseInt()、正则截取字符串中数字
    Git常用操作
    【花费9毛钱购买阿里云服务器搭建一个私有云盘-owncloud】
    js 锚点定位的方法
    【毕业设计】前后端分离——解决cookies跨域
    python flask_restful “message“: “Failed to decode JSON object: None“
    web前端期末大作业:基于html化妆品购物商城项目的设计与实现——化妆品官方网站设计与实现(HTML+CSS+JS)
  • 原文地址:https://blog.csdn.net/IsayIwant/article/details/126651845