看到这题,我的第一反应是:这就是一个费用流模型?用模拟费用流的方法?

这应该是可以的,但是我忘记了怎么模拟费用流了IOI不可能考模拟费用流。于是我就想了另外一个方法。

首先我们考虑模拟费用流的模型如下图:

直接费用流复杂度比较大,我们把它换成一个dp。设\(f_{i, j}\)表示考虑了前\(i\)个点,且\(i\)个点后面一条在图中横着的边的流量为\(j\)的时候,最小费用是多少。注意这里从左到右的流量记为正,否则记为负。转移的时候如果第\(i\)个点是红点,就枚举\(S\)向这个点连的边的流量,否则枚举这个点向\(T\)连的边的流量。根据流量平衡方程我们算出前一条横着的边的流量。

用前缀/后缀min将这个算法优化至\(O((r + b)^2)\),可以获得\(7\)分的成绩。

代码如下:

  1. #include "wiring.h"
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. const int N = 205, M = 205;
  5. const long long inf = 1000000000000000ll;
  6. int n, m;
  7. long long f[N + M][N + M << 2], g[N + M][N + M << 2];
  8. pair<long long, int> vec[N + M];
  9. long long min_total_length(std::vector<int> r, std::vector<int> b) {
  10. n = r.size(), m = b.size();
  11. for (int i = 1; i <= n; i++) vec[i] = make_pair(r[i - 1], 0);
  12. for (int i = 1; i <= m; i++) vec[i + n] = make_pair(b[i - 1], 1);
  13. sort(vec + 1, vec + n + m + 1);
  14. for (int i = 0; i <= (n + m << 2); i++) f[0][i] = inf;
  15. f[0][n + m << 1] = 0ll;
  16. for (int i = 1; i <= n + m; i++) {
  17. for (int j = 0; j <= (n + m << 2); j++) {
  18. f[i][j] = f[i - 1][j];
  19. if (i) {
  20. int tim = j - (n + m << 1);
  21. if (tim < 0) tim = -tim;
  22. f[i][j] += 1ll * (vec[i].first - vec[i - 1].first) * tim;
  23. }
  24. }
  25. if (vec[i].second) {
  26. g[i][0] = f[i][0];
  27. for (int j = 1; j <= (n + m << 2); j++) g[i][j] = min(f[i][j], g[i][j - 1]);
  28. for (int j = 0; j <= (n + m << 2); j++) {
  29. if (!j) f[i][j] = inf;
  30. else f[i][j] = g[i][j - 1];
  31. }
  32. }
  33. else {
  34. g[i][n + m << 2] = f[i][n + m << 2];
  35. for (int j = (n + m << 2) - 1; j >= 0; j--) g[i][j] = min(f[i][j], g[i][j + 1]);
  36. for (int j = 0; j <= (n + m << 2); j++) {
  37. if (j == (n + m << 2)) f[i][j] = inf;
  38. else f[i][j] = g[i][j + 1];
  39. }
  40. }
  41. }
  42. return f[n + m][n + m << 1];
  43. }

注意这里实现的时候用了平移的技巧处理第二维为负数的情况。

接下来我们考虑优化这个dp的方法。

把这个dp状态的第二维看成一个函数,那么我们会发现,需要进行的操作有:函数向左或向右平移一个单位,给它取前缀\(\min\),给它取后缀\(\min\),以及给它加上\(k \lvert x \rvert\)。

容易发现这些操作都不会改变函数下凸的性质。因此我们可以用APIO2016T2,我自己出的名为“穿越”的联测题等题目的方法。用一个set/multiset维护这个函数的每个拐点的位置以及斜率的变化值,再用\(O(1)\)的变量维护最左边/右边的那一段的斜率和截距,再维护偏移量(为了进行平移操作),就可以实现平移操作和加\(k \lvert x \rvert\)操作。而取前缀\(\min\)操作相当于是把一个函数图像的右边递增的一段变为常值函数,如下图所示:

因此我们可以在set/multiset上不断删除右边的拐点,直到右边那一段斜率刚好\(\ge 0\)(也就是再删去一个就\(<0\)了)为止。然后在改变恰好一个拐点的斜率变化量就可以实现前缀\(\min\)操作。同理我们可以实现后缀\(\min\)操作。

注意到这里的复杂度可以被拐点个数的减少量bound住,所以总复杂度仍然为\(O(n \log n)\)。

满分代码如下:

  1. #include "wiring.h"
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. const int N = 100005, M = 100005;
  5. const long long inf = 1000000000ll;
  6. int n, m;
  7. pair<long long, int> vec[N + M];
  8. multiset<pair<int, long long> > que;
  9. long long min_total_length(std::vector<int> r, std::vector<int> b) {
  10. n = r.size(), m = b.size();
  11. for (int i = 1; i <= n; i++) vec[i] = make_pair(r[i - 1], 0);
  12. for (int i = 1; i <= m; i++) vec[i + n] = make_pair(b[i - 1], 1);
  13. sort(vec + 1, vec + n + m + 1);
  14. int val = 0;
  15. long long k_l = -inf, k_r = inf, val_l = inf * (n + m);
  16. que.insert(make_pair(0, inf << 1));
  17. for (int i = 1; i <= n + m; i++) {
  18. if (i > 1) {
  19. que.insert(make_pair(val, (vec[i].first - vec[i - 1].first) << 1));
  20. k_l -= vec[i].first - vec[i - 1].first, k_r += vec[i].first - vec[i - 1].first;
  21. val_l += (vec[i].first - vec[i - 1].first) * (n + m + val);
  22. }
  23. if (vec[i].second) {
  24. val++;
  25. while (k_l < 0ll) {
  26. pair<int, long long> pi = *que.begin();
  27. if (k_l + pi.second < 0ll) {
  28. val_l -= pi.second * (pi.first + n + m);
  29. k_l += pi.second;
  30. }
  31. else {
  32. val_l += k_l * (pi.first + n + m);
  33. que.insert(make_pair(pi.first, k_l + pi.second));
  34. k_l = 0ll;
  35. }
  36. que.erase(que.find(pi));
  37. }
  38. }
  39. else {
  40. val--;
  41. while (k_r > 0ll) {
  42. pair<int, long long> pi = *que.rbegin();
  43. if (k_r - pi.second > 0ll) k_r -= pi.second;
  44. else {
  45. que.insert(make_pair(pi.first, pi.second - k_r));
  46. k_r = 0ll;
  47. }
  48. que.erase(que.find(pi));
  49. }
  50. }
  51. }
  52. int lst = -n - m;
  53. long long ans = val_l;
  54. for (multiset<pair<int, long long> > :: iterator it = que.begin(); it != que.end(); it++) {
  55. pair<int, long long> pi = *it;
  56. if (pi.first < val) {
  57. ans += k_l * (pi.first - lst);
  58. k_l += pi.second, lst = pi.first;
  59. }
  60. else {
  61. ans += k_l * (val - lst);
  62. lst = val;
  63. break;
  64. }
  65. }
  66. if (lst < val) ans += k_l * (val - lst);
  67. return ans;
  68. }

「IOI2017」接线 的另类做法的更多相关文章

  1. 「IOI2017」西默夫 的一个另类做法

    我们发现如果我们有一个环套树的话,那么我们可以把这个环套树去掉每一条环上的边\(e\),问一遍有多少御道在这棵树上.假设删去\(e\)后答案为\(A_e\). 如果答案全部一样,那么说明环上的边都不在 ...

  2. LOJ #2135. 「ZJOI2015」幻想乡战略游戏(点分树)

    题意 给你一颗 \(n\) 个点的树,每个点的度数不超过 \(20\) ,有 \(q\) 次修改点权的操作. 需要动态维护带权重心,也就是找到一个点 \(v\) 使得 \(\displaystyle ...

  3. JavaScript OOP 之「创建对象」

    工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程.工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题. function createPers ...

  4. 「C++」理解智能指针

    维基百科上面对于「智能指针」是这样描述的: 智能指针(英语:Smart pointer)是一种抽象的数据类型.在程序设计中,它通常是经由类型模板(class template)来实做,借由模板(tem ...

  5. 「HNOI2016」数据结构大毒瘤

    真是 \(6\) 道数据结构毒瘤... 开始口胡各种做法... 「HNOI2016」网络 整体二分+树状数组. 开始想了一个大常数 \(O(n\log^2 n)\) 做法,然后就被卡掉了... 发现直 ...

  6. Loj #3057. 「HNOI2019」校园旅行

    Loj #3057. 「HNOI2019」校园旅行 某学校的每个建筑都有一个独特的编号.一天你在校园里无聊,决定在校园内随意地漫步. 你已经在校园里呆过一段时间,对校园内每个建筑的编号非常熟悉,于是你 ...

  7. 「HNOI2016」序列 解题报告

    「HNOI2016」序列 有一些高妙的做法,懒得看 考虑莫队,考虑莫队咋移动区间 然后你在区间内部找一个最小值的位置,假设现在从右边加 最小值左边区间显然可以\(O(1)\),最小值右边的区间是断掉的 ...

  8. 「ZJOI2015」地震后的幻想乡 解题报告

    「ZJOI2015」地震后的幻想乡 想了半天,打开洛谷题解一看,最高票是_rqy的,一堆密密麻麻的积分差点把我吓跑. 据说有三种解法,然而我只学会了一种最辣鸡的凡人解法. 题意:给一个无向图\(G\) ...

  9. 「TJOI2015」概率论 解题报告

    「TJOI2015」概率论 令\(f_i\)代表\(i\)个点树形态数量,\(g_i\)代表\(i\)个点叶子个数 然后列一个dp \[ f_i=\sum_{j=0}^{i-1} f_j f_{i-j ...

随机推荐

  1. tcp 保活定时器分析 & Fin_WAIT_2 定时器

    tcp keepalive定时器 http server 和client端需要防止"僵死"链接过多!也就是建立了tcp链接,但是没有报文交互, 或者client 由于主机突然掉电! ...

  2. nginx&http 第三章 ngx 事件http 初始化1

    在 http 配置块中,我们配置了 http 连接相关的信息,HTTP 框架也正是从这里启动的 在 nginx 初始化的过程中,执行了 ngx_init_cycle 函数,其中进行了配置文件解析,调用 ...

  3. c++中的几种函数调用约定(转)

    C++中的函数调用约定(调用惯例)主要针对三个问题: 1.参数传递的方式(是否采用寄存器传递参数.采用哪个寄存器传递参数.参数压桟的顺序等): 参数的传递方式,最常见的是通过栈传递.函数的调用方将参数 ...

  4. 谷歌Colab使用(深度学习)

    1. Coalb简介 Google Colaboratory是谷歌开放的一款研究工具,主要用于机器学习的开发和研究.这款工具现在可以免费使用,但是不是永久免费暂时还不确定.Google Colab最大 ...

  5. K尾相等数(模运算)

    Description 从键盘输入一个自然数K(K>1),若存在自然数M和N(M>N),使得K^M^和K^N^均大于或等于1000,且他们的末尾三位数相等,则称M和N是一对"K尾 ...

  6. 状态模式(Established close)

    状态模式(Established close) 引子 铁扇公主:以前陪我看月亮的时候,叫人家小甜甜,现在新人胜旧人了,叫人家牛夫人! 定义 Allow an object to alter its b ...

  7. powertool

    powertool简介 PowerTool 一款免费强大的进程管理器,支持进程强制结束,可以Unlock占用文件的进程,查看文件/文件夹被占用的情况,内核模块和驱动的查看和管理,进程模块的内存的dum ...

  8. 分布式监控系统之Zabbix 使用SNMP、JMX信道采集数据

    前文我们了解了zabbix的被动.主动以及web监控相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/14024212.html:今天我们来了解下zabb ...

  9. 如何使用ABBYY FineReader 的用户模式?

    在运用ABBYY FineReader 15(Windows系统)进行文档识别时,用户可能会遇到识别的文档包含一些特殊字符或者其他软件无法识别的字体等情况,容易造成识别出现乱码的结果.在这种情况下,用 ...

  10. 「LOJ #6500」「雅礼集训 2018 Day2」操作

    description LOJ 6500 solution 根据常有套路,容易想到将区间差分转化为异或数组上的单点修改,即令\(b_i=a_i \ xor\ a_{i-1}\), 那么将\([l,l+ ...