题目链接

http://uoj.ac/contest/47/problem/455

题解

模拟费用流,一个非常神奇的东西。

本题即为WC2019 laofu的讲课中的Problem 8,经典的老鼠进洞模型,洞有容量和额外权值。

这道题的Subtask 4,5,6,7分别对应着老鼠进洞的最基础模型、洞有额外权值、洞有容量、洞有容量和额外权值四个变形。

让我们从最简单的开始各个击破。

Subtask 4

注: 本部分配合WC2019课件里的代码图理解效果更佳。

数轴上有\(n\)只老鼠(坐标\(x_i\))和\(m\)个洞(坐标\(y_i\)),每个老鼠必须进一个洞,每个洞至多进一只老鼠,最小化距离和。

假设洞\(i\)匹配了老鼠\(j\), 则若\(y_i<x_j\)代价为\(x_j+(-y_i)\), 否则为\((-x_j)+y_i\),不妨拆成\(i,j\)两部分分别计算。

使用可撤销贪心的策略。

维护一个老鼠堆和一个洞堆,从左到右扫描。

若当前扫到一只老鼠\(i\),那么先随便匹配一个目前空闲的洞(不妨假设负无穷远处有无穷多个洞),从洞堆中取出一个元素\(j\)匹配,其代价为\(x_i+j\)

考虑当后面插入洞的时候允许老鼠反悔(像极了费用流的反向边)而匹配新的洞,那么如果一个老鼠反悔而匹配了右面的新洞\(k\), 那么代价的增量为\((y_k-x_i)-(x_i+j)=y_k+(-2x_i-j)\), 所以往老鼠堆中插入元素\(-2x_i-j\).

若当前扫到一个洞\(j\),考虑是否有老鼠要反悔,从老鼠堆中取出一个元素\(i\), 若反悔的代价\(y_j+i>0\)那么让这个老鼠堆中的老鼠反悔,同时往洞堆中加入元素\(-2y_j-i\),表示后面的老鼠再匹配这个洞的代价;否则只往洞堆中插入元素\(-y_j\).

时间复杂度\(O(n\log n)\).

Subtask 5

洞有额外权值\(w\),怎么办呢?

考虑这样和原来有什么区别: 原来只有老鼠会反悔(因为不会有舍近求远故意匹配较远洞的情况),但是现在可能出现了!

解决办法:让洞和洞匹配,洞也可以反悔。

插入洞\(i\)时,除了往洞堆中插入元素之外,往老鼠堆也插入元素,表示该洞反悔的代价。插入的元素是\(-y_i-w_i\), 相当于用新洞\(j\)替代该洞会少这么多的代价再加上\(y_j+w_j\).

坑:WC课件中那个“老鼠和洞同时反悔不优”目前没看明白。

Subtask 6

洞有容量。

有一个神仙做法,见WC课件或UOJ题解。

正常做法: 往堆里存一个pair, 记录价值和剩余容量。

每次增广会使一个往左走的老鼠改为往右走,因此复杂度正确。

Subtask 7

洞有容量,也有附加权值,显然Subtask 6的做法时间复杂度错误。

但是我们发现,如果两个老鼠\(i,j\)分别匹配洞\(k\)且\(x_i<x_j<y_k\), 那么这两种情况下往洞堆中丢的元素是一样的,都是\(-y_k-w_k\).

所以我们可以“批量加入、批量增广”,这样复杂度就得到了保证。

其实这个东西就已经非常像费用流增广的过程了。

费用流的一条重要性质是: 如果不是每次选全局最短路,而是按另一顺序对和源点相连的边必须连的点进行增广,那么也是正确的。

建议结合代码以获取更好理解。

代码

代码写出来发现和费用流神相似!

  1. #include<cstdio>
  2. #include<cstdlib>
  3. #include<iostream>
  4. #include<queue>
  5. #include<algorithm>
  6. #define llong long long
  7. using namespace std;
  8. const int N = 1e5;
  9. const llong INF = 1000000000000ll;
  10. struct Node
  11. {
  12. int w; llong c;
  13. Node() {}
  14. Node(int _w,llong _c) {w = _w,c = _c;}
  15. bool operator <(const Node &arg) const {return c>arg.c;}
  16. };
  17. struct Element
  18. {
  19. llong x,w,c;
  20. } a[N+3],b[N+3];
  21. priority_queue<Node> mouse,hole;
  22. int n,m;
  23. llong ans;
  24. void insertmouse(int x)
  25. {
  26. llong ret = INF;
  27. if(!hole.empty())
  28. {
  29. Node tmp = hole.top();
  30. ret = tmp.c+x;
  31. hole.pop(); if(tmp.w-1>0) {hole.push(Node(tmp.w-1,tmp.c));}
  32. }
  33. ans += ret;
  34. mouse.push(Node(1,-x-ret));
  35. }
  36. void inserthole(int x,int w,llong c)
  37. {
  38. int rst = w;
  39. while(!mouse.empty() && rst>0)
  40. {
  41. Node tmp = mouse.top();
  42. llong val = x+c+tmp.c;
  43. if(val>=0) break;
  44. int flow = min(rst,tmp.w);
  45. ans += val*flow; rst -= flow;
  46. hole.push(Node(flow,-val-x+c));
  47. mouse.pop(); if(tmp.w-flow>0) {mouse.push(Node(tmp.w-flow,tmp.c));}
  48. }
  49. if(rst>0) {hole.push(Node(rst,-x+c));}
  50. if(w-rst>0) {mouse.push(Node(w-rst,-x-c));}
  51. }
  52. int main()
  53. {
  54. scanf("%d%d",&n,&m); llong sum = 0ll;
  55. for(int i=1; i<=n; i++) {scanf("%lld",&a[i].x);}
  56. for(int i=1; i<=m; i++) {scanf("%lld%lld%lld",&b[i].x,&b[i].c,&b[i].w); sum += b[i].w;}
  57. if(sum<n) {printf("-1"); return 0;}
  58. int i = 1,j = 1;
  59. while(i<=n||j<=m)
  60. {
  61. if(i<=n && (j>m||a[i].x<b[j].x)) {insertmouse(a[i].x); i++;}
  62. else {inserthole(b[j].x,b[j].w,b[j].c); j++;}
  63. }
  64. printf("%lld\n",ans);
  65. return 0;
  66. }

UOJ #455 [UER #8]雪灾与外卖 (贪心、模拟费用流)的更多相关文章

  1. [UOJ455][UER #8]雪灾与外卖——堆+模拟费用流

    题目链接: [UOJ455]雪灾与外卖 题目描述:有$n$个送餐员(坐标为$x_{i}$)及$m$个餐厅(坐标为$y_{i}$,权值为$w_{i}$),每个送餐员需要前往一个餐厅,每个餐厅只能容纳$c ...

  2. luogu P5470 [NOI2019]序列 dp 贪心 费用流 模拟费用流

    LINK:序列 考虑前20分 容易想到爆搜. 考虑dp 容易设\(f_{i,j,k,l}\)表示前i个位置 选了j对 且此时A选择了k个 B选择了l个的最大值.期望得分28. code //#incl ...

  3. 贪心(模拟费用流):NOIP2011 观光公交

    [问题描述] 风景迷人的小城Y 市,拥有n 个美丽的景点.由于慕名而来的游客越来越多,Y 市特意安排了一辆观光公交车,为游客提供更便捷的交通服务.观光公交车在第0 分钟出现在1号景点,随后依次前往2. ...

  4. BZOJ4977[Lydsy1708月赛]跳伞求生——贪心+堆+模拟费用流

    题目链接: 跳伞求生 可以将题目转化成数轴上有$n$个人和$m$个房子,坐标分别为$a_{i}$和$b_{i}$,每个人可以进一个他左边的房子,每个房子只能进一个人.每个房子有一个收益$c_{i}$, ...

  5. 模拟费用流 & 可撤销贪心

    1. CF730I Olympiad in Programming and Sports 大意: $n$个人, 第$i$个人编程能力$a_i$, 运动能力$b_i$, 要选出$p$个组成编程队, $s ...

  6. 题解-UOJ 455雪灾与外卖

    Problem \(\mathrm{UOJ~455}\) 题意概要:一根数轴上有 \(n\) 只老鼠与 \(m\) 个洞,每个洞有费用与容量限制,要求每只老鼠要进一个洞且每个洞的老鼠不超过自身的容量限 ...

  7. uoj455 【UER #8】雪灾与外卖

    http://uoj.ac/problem/455 题解: https://blog.csdn.net/litble/article/details/88410435 https://www.mina ...

  8. 贪心+模拟 Codeforces Round #288 (Div. 2) C. Anya and Ghosts

    题目传送门 /* 贪心 + 模拟:首先,如果蜡烛的燃烧时间小于最少需要点燃的蜡烛数一定是-1(蜡烛是1秒点一支), num[g[i]]记录每个鬼访问时已点燃的蜡烛数,若不够,tmp为还需要的蜡烛数, ...

  9. UVALive 4863 Balloons 贪心/费用流

    There will be several test cases in the input. Each test case will begin with a line with three inte ...

随机推荐

  1. C++ 的关键字(保留字)完整介绍

    转载至:https://www.runoob.com/w3cnote/cpp-keyword-intro.html 1. asm asm (指令字符串):允许在 C++ 程序中嵌入汇编代码. 2. a ...

  2. bfs(太空电梯)

    http://oj.jxust.edu.cn/contest/problem?id=1563&pid=4 题目描述 公元9012年,Q博士发明了一部太空电梯,与一般电梯不同,太空电梯不能直接到 ...

  3. python-day40(正式学习)

    目录 线程队列 1 2 3 线程定时器 进程池和线程池 线程队列 1 import queue q=queue.Queue() q.put('123') q.put('456') q.put('789 ...

  4. Django重写用户模型报错has no attribute 'USERNAME_FIELD'

    目录 Django重写用户模型报错has no attribute 'USERNAME_FIELD' 在重写用户模型时报错:AttributeError: type object 'UserProfi ...

  5. Postman之简单使用

    前提:已获得接口文档 / 抓包数据 1.启动Postman 直接在这个页面输入数据(不用管其他的地方!!!) 2.按照接口文档填入 注意蓝色框中的数据 请求方式:POST(几乎都是使用POST/GET ...

  6. springboot2整合zookeeper集成curator

    步骤: 1- pom.xml <dependency> <groupId>org.apache.curator</groupId> <artifactId&g ...

  7. linux命令详解——lsof

    lsof全名list opened files,也就是列举系统中已经被打开的文件.我们都知道,linux环境中,任何事物都是文件, 设备是文件,目录是文件,甚至sockets也是文件.所以,用好lso ...

  8. Delphi Opendialog组件

  9. flume--为搬砖而生,日志传输的一把好手

    (一)flume的产生 为什么会有flume 随着互联网的发展,人们对网络日志产生的信息也越来越重视.不仅如此,我们的服务器,比如Nginx,每天都会产生大量的日志.我们要将这些日志收集到指定的地方, ...

  10. Java入门指南-04 顺序、分支、循环

    顺序结构 从上至下,依次执行 if 语句在 Java 里,用 if 语句来实现“当满足 XXX 条件时,执行 YYY”这样的逻辑判断.例如,在使用共享单车时需要检查人的年纪.如果在 12 岁以下,则禁 ...