忘记贴个中文题面了~~

作为前来上海大学参加ICPC比赛的退役ACMer现役JBer,小明每次来上海大学参加比赛,都会打铁。心里就会产生一种"这是什么JB比赛"的念头。为了排解痛苦,他就会去上海的迪士尼坐过山车。他很喜欢这种血压拉满的感觉,让他感觉自己如同变成了一只"快乐"的Flappy Bird,忘记了所有的WA和TLE……
过山车有一系列的高低起伏的转折点,各个转折点的高度,按照路径顺序形成了一个数列a[],小明坐完所有过山车后,他的血压可以认为是Σ(|a[i + 1] - a[i]|),即相邻过山车高度的绝对值之和。
小明比赛一直打铁,一直打铁,于是一直需要不停地坐过山车,然而,可以使得他忘记烦恼的所需血压阈值也在慢慢上升。渐渐地,上海迪士尼的过山车,已经达不到小明对自己血压的要求了。
因此,小明开始设想增加额外的m个转折点b[],以任意的位置和顺序(此处需要讨论具体规则),加入原有的过山车的路径a[]中,让自己的血压拉得尽可能高。
请计算一下,小明的血压最高可以变成多少呢?这非常重要。我们需要这个数据来请一位适合的心血管内科医生。救救小明吧!您的AC非常重要!


再反手给一个知乎的原问题链接 ——

https://www.zhihu.com/question/355256075/answer/907869387

当然,事情前前后后的链接可多了2333 ~


以下内容的发表时间是 —— 2019年11月25日 20:30:07

血压游戏,实质名归好吧233333~
代码时间是 2019-11-23 18:34:27一个标点符号我都没有修改,贴在这里了。询问了出题组包括bin巨,他们同意我把标程发过来。
不排除有错误,我觉得很有可能会被发现哪里写得有问题,但是能学到知识不是美滋滋吗 ~
而且确实,在我看来,就与我写了更优复杂度的标程没什么区别,除了n的设置外,没有影响比赛结果。问心无愧。// #include <bits/stdc++.h>
#include<stdio.h>
#include<iostream>
#include<string>
#include<string.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<algorithm>
#include<time.h>
using namespace std;
#define FMS(x, y, g) memset(x, y, sizeof(x[0]) * (g))
typedef long long LL;// Variables For the Specific Problem
const bool GUESS = false;                // 是否验证一些猜想(如点数、边数等)的开关
const bool DEBUG = false;               // 是否输出细节(用于调试)的开关
// control variables end
const LL INF = 1e16;                    // 表示两点之前的极大收益 [1E9是不够的哦]
const int E9 =  1e9;                    // 就表示 1E9
const int G = 100000;                     // 题目设置的数组长度
const int N = G * 4 + 99;               // 点数 4n 级别
const int M = G * 10 * 2 + 99;          // 边数 10n 级别
int n, m;                               // n 表示原始数组 a 的大小,m 表示可插入集合 b 的大小
int a[N], b[N];                         // 待插入数组 a, 可插入集合 b
int casenum, casei;                     // 数据组数 casenum, 当前数据编号 casei
// End// 把 a 或 b 的信息整合一起的数据类型
struct Ele
{int tp, id;int l, r;Ele(){};Ele(int tp_, int id_, int l_, int r_) {tp = tp_; id = id_; l = l_; r = r_;};
};
// 按照左界下降排序
bool cmpL(Ele a, Ele b) {if(a.l != b.l)return a.l > b.l;return a.tp > b.tp;
}
// 按照右界上升排序
bool cmpR(Ele a, Ele b) {if(a.r != b.r)return a.r < b.r;return a.tp > b.tp;
}// 费用流
struct wkcMCMF {int ST, ED;                             // 源点与汇点(我习惯上使得ST=0,ED=最后一个点的编号)int first[N], ID;                       // 边集的起点边编号(ID初始化为1,表示新分配的边的编号)int w[M], cap[M], cost[M], nxt[M];      // 边包括了(抵达点w,容量cap,单位流量成本cost,下条边编号nxt)等信息LL f[N];                               // f[x]表示在残量网络下,从源点到达x的最小距离int pe[N];                                // pe[x]记录流向x的前驱边bool e[N];                             // e[x]是判定点x是否在SPFA队列中的辅助数组queue<int> q;                           // q是SPFA的队列int SUM;                                // 用于检查程序正确性——求最终血压值// 加边void ins(int x, int y, int cap_, LL cost_) {w[++ID] = y;cap[ID] = cap_;cost[ID] = cost_;nxt[ID] = first[x];first[x] = ID;w[++ID] = x;cap[ID] = 0;cost[ID] = -cost_;nxt[ID] = first[y];first[y] = ID;}// 入队void inq(int x, LL cost_, int pe_) {if (cost_ <= f[x])return;           // 单位流量收益更小,没有更新意义f[x] = cost_;                       // 单位流量收益大的条件下做更新pe[x] = pe_;                        // 然后记录上一条边if (x == ED || e[x])return;         // SPFA的入队标记以防止重复入队做冗余更新e[x] = true;q.push(x);}// SPFA找最长路(最高收益)int Augmenting = 1;                     // 表示具有增广意义的最低增广收益值,可能会有调整为 1 或者 0 的需要bool spfa() {                           // 返回值为true表示找到了一条成功的增广路FMS(f, -63, ED + 2);         // 初始的收益值一定要设置为极小 [-1似乎是不够的]cap[0] = E9;inq(ST, 0, 0);while (!q.empty()) {int x = q.front();q.pop();e[x] =  0;for (int z = first[x]; z; z = nxt[z]) {if (cap[z])inq(w[z], f[x] + cost[z], z);}}return f[ED] >= Augmenting;         // 收益 < Augmenting 之后的增广便不再有意义了}// 从终点滚到起点,确定此费用下的最大流量,并修改残余流量 [单路回溯的普通写法]vector<LL>slowMCMF(LL basic) {vector<LL>rtn;int maxflow = 0;LL mincost = basic;while (spfa()) {int flow = E9;int x = ED;while (x != ST) {flow = min(flow, cap[pe[x]]);x = w[pe[x] ^ 1];}maxflow += flow;x = ED;while (x != ST) {cap[pe[x]] -= flow;cap[pe[x] ^ 1] += flow;x = w[pe[x] ^ 1];}for(int i = 1; i <= flow; ++i) {mincost += f[ED];rtn.push_back(mincost);}}while(rtn.size() < m) {rtn.push_back(mincost);}return rtn;}// 高效费用流所使用的DFS多路回溯算法bool vis[N];int dfs(int x, int all) {if (x == ST)return all;int use = 0;vis[x] = true;for (int z = first[x]; z; z = nxt[z])if (cap[z ^ 1]) {int y = w[z];if (!vis[y] && f[y] + cost[z ^ 1] == f[x]) {int tmp = dfs(y, min(cap[z ^ 1], all - use));cap[z ^ 1] -= tmp;cap[z] += tmp;use += tmp;if (use == all)break;}}return use;}// 从终点滚到起点,确定此费用下的最大流量,并修改残余流量 [多路回溯的DFS高效写法]vector<LL> fastMCMF(LL basic) {vector<LL>rtn;int maxflow = 0;LL mincost = basic;while (spfa()) {int flow;while (FMS(vis, 0, ED + 2),flow = dfs(ED, E9)) {maxflow += flow;for(int i = 1; i <= flow; ++i) {mincost += f[ED];rtn.push_back(mincost);}}}while(rtn.size() < m) {rtn.push_back(mincost);}return rtn;}// ST = 0// added point [1, m]// up segment point [m + 1, m + n)// down segment point [m + n + 1, m + n + n)// final [m + n + n + 0, m + n + n + n]// ED = m + n * 3 + 1Ele ele[N];map<int, int>rkL;                   // 记录每个左界区间对应的排名(start from 1)map<int, int>rkR;                   // 记录每个右界区间对应的排名(start from 1)pair<int, int>vaL[N];               // sorted L list (vaL, id) downpair<int, int>vaR[N];               // sorted R list (vaR, id) upint pos[N];                         // a to b, record the ansvector<int>ansVec;                  // ans vectorvoid NplusM_mapBuild() {ID = 1;ST = 0;ED = m + n * 3 + 1;int eg = 0;for (int i = 1; i <= m; ++i) {ele[++eg].tp = 1;ele[eg].id = i;ele[eg].l = ele[eg].r = b[i];}for (int i = 1; i < n; ++i) {ele[++eg].tp = 2;ele[eg].id = i;ele[eg].l = min(a[i], a[i + 1]);ele[eg].r = max(a[i], a[i + 1]);}rkL.clear();rkR.clear();// point -> bigger segment [L decreasing order]// 从区间 [l[i + 1], r[i + 1]] 向区间 [l[i], r[i]] 连一条容量极大(>=m即可),收益为(l[i] - l[i + 1]) * 2的边 <此处n - 2条边>int lastId = E9;int lastV;int oL = 0;sort(ele + 1, ele + eg + 1, cmpL);for(int i = 1; i <= eg; ++i) {if(ele[i].tp == 2) {int eid = ele[i].id + m;if(lastId != E9) {ins(eid, lastId, E9, (lastV - ele[i].l) << 1);}lastId = eid;lastV = ele[i].l;vaL[++oL] = {lastV, ele[i].id};rkL[lastV] = oL;}else if(lastId != E9) {// 向比其大的第一个(如果存在) "下匹配的左界区间[l, r]", 连一条容量为1(>=1即可),收益为(l - v) * 2的边ins(ele[i].id, lastId, 1, (lastV - ele[i].l) << 1);}}if (DEBUG) {printf("rkL(v,g): ");for(auto &it : rkL)printf("[%d %d] ", it.first, it.second);puts("");}// point -> smaller segment [R increasing order]// 从区间 [l[i + 1], r[i + 1]] 向区间 [l[i], r[i]] 连一条容量极大(>=m即可),收益为(r[i + 1] - r[i]) * 2的边 <此处n - 2条边>lastId = E9;int oR = 0;sort(ele + 1, ele + eg + 1, cmpR);for (int i = 1; i <= eg; ++i) {if (ele[i].tp == 2) {int eid = ele[i].id + m + n;if(lastId != E9) {ins(eid, lastId, E9, (ele[i].r - lastV) << 1);}lastId = eid;lastV = ele[i].r;vaR[++oR] = {lastV, ele[i].id};rkR[lastV] = oR;}else if (lastId != E9) {// 向比其大的第一个(如果存在) "下匹配的左界区间[l, r]", 连一条容量为1(>=1即可),收益为(v - r) * 2的边ins(ele[i].id, lastId, 1, (ele[i].r - lastV) << 1);}}if (DEBUG) {printf("rkR(v,g): ");for(auto &it : rkR)printf("[%d %d] ", it.first, it.second);puts("");}// 从 m 个插入点向首尾 2 个特殊插入位置连一条容量为1(>=1即可),收益为0的边 <此处m * 2条边>for (int i = 1; i <= m; ++i) {ins(i, m + n + n + 0, 1, abs(b[i] - a[1]));ins(i, m + n + n + n, 1, abs(b[i] - a[n]));}// 源点连接 m 个被插入点[1, m], 容量统一为1,收益统一为0 <此处m条边>for (int i = 1; i <= m; ++i) {ins(ST, i, 1, 0);}// 下匹配左区间点 & 上匹配右区间点 -> 真实区间点 <此处(n-1)*2条边>for (int i = 1; i < n; ++i) {ins(m + i, m + n + n + i, 1, 0);ins(m + n + i, m + n + n + i, 1, 0);}// 真实区间点 [m + n + n + 0, m + n + n + n] 向汇点 ED 连一条容量为1,收益为0的边 <此处n + 1条边>for (int i = 0; i <= n; ++i) {ins(m + n + n + i, ED, 1, 0);}}// 根据网络流的流量情况构造解int from[N];                    // 记录每个实际的区间a是作为上升区间还是下降区间被匹配的void NplusM_output() {set<int>AnyPosOK_bset;for (int i = 1; i <= m; ++i) {AnyPosOK_bset.insert(b[i]);}vector<Ele>LbTOa;vector<Ele>RbTOa;for (int y = 0; y <= n; ++y) {// check[m + 1, m + n) && [m + n + 1, m + n + n)if (y != 0 && y != n) {int va = min(a[y], a[y + 1]);for (int z = first[m + y]; z; z = nxt[z]) {if (z % 2 == 1 && cap[z] != 0 && w[z] <= m) {int vb = b[w[z]];int va = min(a[y], a[y + 1]);AnyPosOK_bset.erase(vb);LbTOa.push_back({vb, rkL[va], va, va});if (DEBUG) {printf("LbTOa: (b = %d a = %d) va = %d vb = %d\n", w[z], y, va, vb);}}}va = max(a[y], a[y + 1]);for (int z = first[m + n + y]; z; z = nxt[z]) {if (z % 2 == 1 && cap[z] != 0 && w[z] <= m) {int vb = b[w[z]];AnyPosOK_bset.erase(vb);RbTOa.push_back({vb, rkR[va], va, va});if (DEBUG) {printf("RbTOa: (b = %d a = %d) va = %d vb = %d\n", w[z], y, va, vb);}}}// 查询每个区间实际是匹配的上升区间还是下降区间from[y] = 0;for (int z = first[m + n + n + y]; z; z = nxt[z]) {if (z % 2 == 1 && cap[z] != 0) {if (w[z] > m && w[z] <= m + n) {from[y] = 1;} else if (w[z] > m + n && w[z] <= m + n + n) {from[y] = 2;}}}}// 这里其实只有 y == 0 或 y == n 时才会被直接从[1, m]产生流量 [TODO——验证猜想]for (int z = first[m + n + n + y]; z; z = nxt[z]) {if (z % 2 == 1 && cap[z] != 0 && w[z] <= m) {int vb = b[w[z]];AnyPosOK_bset.erase(vb);break;}}}// In LbTOa or RbTOa, {tp:vb, id:rk, l:va, r:va}, the same value makes rk seem to be bigger// In vaL or vaR, {vL or vR, id}]// vaL[i], the i-th smallest val and pos// vaR[i], the i-th biggest val and posFMS(pos, 0, n + 2);int LpreID = 0;sort(LbTOa.begin(), LbTOa.end(), cmpL); // vaL is going down after sortingfor(auto &it : LbTOa) {LpreID = min(it.id, LpreID + 1);while(true) {int p = vaL[LpreID].second;if (from[p] != 1)++LpreID;else {pos[p] = it.tp;break;}}}int RpreID = 0;sort(RbTOa.begin(), RbTOa.end(), cmpR); // vaR is going up after sortingfor(auto &it : RbTOa) {it.id = min(it.id, RpreID + 1);RpreID = it.id;while(true) {int p = vaR[RpreID].second;if (from[p] != 2)++RpreID;else {pos[p] = it.tp;break;}}}if (DEBUG) {printf("POS: ");for(int i = 1; i < n; ++i)printf("%d ", pos[i]);puts("");}// ans outputansVec.clear();for (int y = 0; y <= n; ++y) {if (y) {ansVec.push_back(a[y]);}// possibility 1: matched alreadyif (y != 0 && y != n && pos[y]) {ansVec.push_back(pos[y]);continue;}// possibility 2: head or tailbool flag = 0;if (y == 0 || y == n) {for (int z = first[m + n + n + y]; z; z = nxt[z]){if (z % 2 == 1 && cap[z] != 0 && w[z] <= m) {ansVec.push_back(b[w[z]]);flag = 1;break;}}}// possibility 3: any position is the same to some elementsif(!flag && AnyPosOK_bset.size()) {ansVec.push_back(*AnyPosOK_bset.begin());AnyPosOK_bset.erase(AnyPosOK_bset.begin());}}SUM = 0;for(int i = 0; i < ansVec.size(); ++i) {printf("%d ", ansVec[i]);if (i)SUM += abs(ansVec[i] - ansVec[i - 1]);}puts("");}// 低效建图法,边数O(nm)void NmultM_mapBuild() {ID = 1;ST = 0;ED = m + 1 + n + 1;for (int i = 1; i <= m; ++i) {int V = b[i];ins(ST, i, 1, 0);for (int j = 0; j <= n; ++j) {int lftV = j == 0 ? V : a[j];int rgtV = j == n ? V : a[j + 1];int oriV = (j == 0 || j == n) ? 0 : abs(a[j + 1] - a[j]);int incV = abs(lftV - V) + abs(rgtV - V) - oriV;ins(i, m + 1 + j, 1, incV);}}for (int i = 0; i <= n; ++i) {ins(m + 1 + i, ED, 1, 0);}}// 低效建图下的解构造void NmultM_output() {// ST = 0// b[] ∈ [1, m]// a[] ∈ [m + 1 + 0, m + 1 + n]// ED = n + m + 2vector<int>ansVec;for (int i = 0; i <= n; ++i) {pos[i] = 0;}for (int x = 1; x <= m; ++x) {for (int z = first[x]; z; z = nxt[z]) {if (z % 2 == 0 && cap[z] == 0) {pos[w[z] - m - 1] = b[x];}}}for (int i = 0; i <= n; ++i) {if(i) ansVec.push_back(a[i]);if(pos[i])ansVec.push_back(pos[i]);}for(auto &it : ansVec) {printf("%d ", it);}puts("");}// 输出实际的流量网络图的DEBUG过程void printMap() {for (int x = 0; x <= ED; ++x) {for (int z = first[x]; z; z = nxt[z]) {if (z % 2 == 0) {int y = w[z];if (cap[z ^ 1] != 0)printf("%d->%d(%d, %d)\n", x, y, cap[z ^ 1], cost[z]);}}}}
}mcmf;//暴力DFS算法
struct My_BF {int rev[1 << 20];LL MAXV;vector<LL>BEST;vector<int>ansVec, vec;void init() {for(int i = 0; i < 20; ++i) {rev[1 << i] = i;}}int lowbit(int x) {return x & -x;}void dfs(int mask, int pos, LL sumV, int num) {if (num == m && sumV > MAXV) {MAXV = sumV;ansVec = vec;}if (sumV > BEST[num - 1]) {BEST[num - 1] = sumV;}if (pos > n) {return;}for (int tmp = mask; tmp; tmp -= lowbit(tmp)) {int o = rev[lowbit(tmp)];int v = b[o + 1];int addV = 0;if (pos == 0) {addV = abs(v - a[pos + 1]);}else if (pos == n) {addV = abs(v - a[pos]);}else {addV = abs(v - a[pos + 1]) + abs(v - a[pos]);}vec.push_back(v); if (pos < n)vec.push_back(a[pos + 1]);dfs(mask - lowbit(tmp), pos + 1, sumV + addV, num + 1);vec.pop_back(); if (pos < n)vec.pop_back();}int addV = 0;if (pos != 0 && pos != n) {addV = abs(a[pos] - a[pos + 1]);}if (pos < n)vec.push_back(a[pos + 1]);dfs(mask, pos + 1, sumV + addV, num);if (pos < n)vec.pop_back();}vector<LL> solve() {init();BEST.resize(m);for (int i = 0; i <= m; ++i) {BEST[i] = -1;}MAXV = -1;dfs((1 << m) - 1, 0, 0, 0);return BEST;}void output() {for (int i = 0; i < ansVec.size(); ++i) {printf("%d ", ansVec[i]);}puts("");}
}bf;// 贪心算法——TODO
struct My_Greedy {LL solve() {return 0;}
}greedy;// 数据生成器
struct My_DataGenerator {void rd_dataGenerator() {srand(time(0));freopen("Blood Pressure Game.in", "w", stdout);casenum = 1000; printf("%d\n", casenum + 0);int smlCasenum = 990;int midDCasenum = 997;for (casei = 1; casei <= casenum; ++casei){int n = rand() % 100 + 1;if (casei > smlCasenum) {n = rand() % 600 + 1;}else if(casei > midDCasenum) {n = 600;}m = rand() % (n + 1) + 1;int TOPV = casei <= smlCasenum ? n * 10 : (rand() % 2 ? 10000 : E9);printf("%d %d\n", n, m);set<int>noEqualSot;for(int i = 1; i <= n; ++i) {do{a[i] = rand() % TOPV + 1;}while(noEqualSot.count(a[i]));noEqualSot.insert(a[i]);printf("%d ", a[i]);}puts("");for(int i = 1; i <= m; ++i) {do{b[i] = rand() % TOPV + 1;}while(noEqualSot.count(b[i]));noEqualSot.insert(b[i]);printf("%d ", b[i]);}puts("");}}
}dataGenerator;struct Checker {string check_it() {auto& ansVec = mcmf.ansVec;set<int>sot;for (int i = 0; i < n + m; ++i) {int v = ansVec[i];if(sot.count(v)) {return "same value element in ansVec[]";}sot.insert(v);}// array checkset<int>bset;for (int i = 1; i <= m; ++i) {bset.insert(b[i]);}int nxtPos = 1;int bnum = 0;for(int i = 0; i < n + m; ++i) {if (nxtPos <= n && a[nxtPos] == ansVec[i]) {++nxtPos;bnum = 0;}else {if(!bset.count(ansVec[i])) {return "it is a wrong final array %d";}bset.erase(ansVec[i]);if (++bnum > 1) {return "can not insert continuous elements of array b";}}}return "AC";}
}checker;void printVec(string str, vector<LL>vec) {if (str != "") printf("%s: ", str.c_str());//cout << str << ": ";for(auto &it : vec) {printf("%lld ", it);}puts("");
}
void printVec(string str, vector<int>vec) {if (str != "") printf("%s: ", str.c_str());//cout << str << ": ";for(auto &it : vec) {printf("%d ", it);}puts("");
}void HumanData()
{srand(time(0));freopen("Human Data.in", "w", stdout);casenum = 8; printf("%d\n", casenum);for (casei = 1; casei <= casenum; ++casei) {set<int>sot;n = 600; m = 601;printf("%d %d\n", n, m);int basic = 5000 + rand() % 1000;int dif = rand() % 3 + 2;a[1] = basic; sot.insert(a[1]);a[2] = basic + 1; sot.insert(a[2]);for(int i = 3; i <= n; ++i){if (i & 1)a[i] = a[i - 2] - dif;else a[i] = a[i - 2] + dif;sot.insert(a[i]);}for(int i = 1; i <= n; ++i) {printf("%d ", a[i]);}puts("");for(int i = 1; i <= m; ++i) {do{b[i] = rand() % (basic + dif * n) + 1;}while(sot.count(b[i]));sot.insert(b[i]);printf("%d ", b[i]);}puts("");}
}int main() {// HumanData(); return 0;// dataGenerator.rd_dataGenerator(); return 0;freopen("Blood Pressure Game.in", "r", stdin); freopen("Blood Pressure Game.out", "w", stdout);// freopen("Human Data.in", "r", stdin);scanf("%d", &casenum); for(casei = 1; casei <= casenum; ++casei) {printf("Case #%d:\n", casei);scanf("%d%d", &n, &m);for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);for (int i = 1; i <= m; ++i) scanf("%d", &b[i]);LL basic = 0;for (int i = 2; i <= n; ++i) {basic += abs(a[i] - a[i - 1]);}// solution 1: fastNetworkFlowbool NplusM_MCMF = true;vector<LL> fastMCMFvec;if (NplusM_MCMF) {mcmf.NplusM_mapBuild();fastMCMFvec = mcmf.fastMCMF(basic);printVec("", fastMCMFvec); //fastMCMFvecmcmf.NplusM_output();FMS(mcmf.first, 0, mcmf.ED + 2);// ans checkerstring check_str = checker.check_it();if(check_str != "AC") {puts(check_str.c_str());for (int i = 1; i <= n; ++i) {printf("%d ", a[i]);}puts("");for (int i = 1; i <= m; ++i) {printf("%d ", b[i]);}puts("");printf("SUM = %lld\n", mcmf.SUM);while(true);}if(DEBUG) {mcmf.printMap();}}// solution 2: slowNetworkFlowbool NmultM_MCMF = false;vector<LL> slowMCMFvec;if (NmultM_MCMF) {mcmf.NmultM_mapBuild();slowMCMFvec = mcmf.slowMCMF(basic);printVec("", slowMCMFvec); // slowMCMFvecmcmf.NmultM_output();FMS(mcmf.first, 0, mcmf.ED + 2);if (NplusM_MCMF && slowMCMFvec != fastMCMFvec) {puts("Error: slowMCMFvec != fastMCMFvec");while(true);}}// solution 3: Brute Force (DFS)bool bruteForce = false;if (bruteForce) {vector<LL> BFvec = bf.solve();printVec("BFvec", BFvec);bf.output();if (NplusM_MCMF && BFvec != fastMCMFvec) {puts("Error: BFvec != fastMCMFvec");while(true);}}// solution 4: Greedy// LL ans_greedy = greedy.solve();}return 0;
}
/*【Trick && Tsukkomi】Input14 521 3 48 3916 66 9 64 36Output36 21 66 3 64 48 9 39 16Input14 410 50 3 61 9 23 5Output1505 10 1 50 3 23 6 9【题意】把 m 个数任意插入到长度为 n 的数组中的缝隙或两侧,在每个位置最多只能插入一个数的条件下,使得相邻数之差的绝对值的和尽可能大。【分析】这道题,题目是将 m 个待插入数值,向 n + 1 个区间做插入。而这个插入其实也就是匹配。这种带权匹配问题,我们可以使用网络流(费用流)算法来解决。因为我们希望最终的差值之和尽可能大,所以这个"费用"此处是"收益",我把它称呼为最大收益最大流好啦。因为匹配的可能是 n * m ,这个图实际构成了"完全二分图",边数是m * (n + 1).然而,面对1000的数据规模,O(nm) 的边数就已经巨大无比了,最终算法的复杂度将会难以吃得消。要怎么办才好呢?我们可以结合这道题的特殊性,优化建图!可以看到——除了首尾这两个特殊的插入位置外,其他所有的插入位置都可以用一个二元对[l, r]来表示。如果插入的数值 v 比 l 小,其实收益只与 l 有关,是 (l - v) * 2如果插入的数值 v 比 r 大,其实收益只与 r 有关,是 (v - r) * 2否则,插入的数值在区间内,则该插入操作不会产生任何收益。显然,我们发现,对于插入区间,笼统来说,是具有 l 越大优、或 r 越小越优的性质的。其实也就是说,如果我们最终做了匹配 v < [l2, r2],那么不可能我们有一个闲置未匹配区间[l1, r1](l1 > l2)的,这样 v 匹配[l1, r1]一定更优。同理,       如果我们最终做了匹配 [l2, r2] > v,那么不可能我们有一个闲置未匹配区间[l1, r1](r1 < r2)的,这样 v 匹配[l1, r1]一定更优而对于两个区间,其替换后价值的收益其实是线性的。如 v < [l2, r2],由[l2, r2]调整为[l1, r1](l1 > l2)的时候,收益是(l1 - l2) * 2发现了这些性质后,我们就可以优化建图啦——(1) 设置源点编号为0,汇点编号为 m + n * 3 + 1(2) 源点连接 m 个被插入点[1, m], 容量统一为1,收益统一为0 <此处m条边>(3) 把所有非两侧的可插入区间,抽象为[m + 1, m + n)这 n - 1 个点,按照左界从大到小(从优到差)排序,我们考虑每个区间都可能匹配(插入)了比它小的数值(v < l)从区间 [l[i + 1], r[i + 1]] 向区间 [l[i], r[i]] 连一条容量极大(>=m即可),收益为(l[i] - l[i + 1]) * 2的边 <此处n - 2条边>(4) 把所有非两侧的可插入区间,抽象为[m + n + 1, m + n + n)这 n - 1 个点,按照右界从小到大(从优到差)排序,我们考虑每个区间能可能匹配(插入)了比它大的数值(v > r)从区间 [l[i + 1], r[i + 1]] 向区间 [l[i], r[i]] 连一条容量极大(>=m即可),收益为(r[i + 1] - r[i]) * 2的边 <此处n - 2条边>(5) 然而,一个区间最多只能匹配一次,即不可能其既作为较大的区间被插入了值,同时由作为最小的区间被插入了值。因此,我们再设置 [m + n + n + 0, m + n + n + n] 这 n + 1 个点,这些点向汇点 ED 连一条容量为1,收益为0的边 <此处n + 1条边>同时,对于i ∈ [1, n), m + i 与 m + n + i 同时向 m + n + n + i 连一条容量为1(>=1即可),收益为0的边 <此处(n - 1) * 2条边>于是,我们控制使得每个区间被最多匹配一次,同时一个区间不可能同时作为较大区间和较小区间同时被匹配插入。(6) 注意到,可以被插入匹配的位置其实有 n + 1 个,而对于首区间和尾区间,编号实际为 m + n + n + 0 和 m + n + n + n,我们直接从 m 个插入点向这 2 个特殊插入位置连一条容量为1(>=1即可),收益为0的边 <此处m * 2条边>(7) 不要忘记了,"向下匹配的左界区间"和"向上匹配的右界区间",虽然它们都被连成了链,且连入了唯一编号的区间,但却没有流量流入。对于 m 个插入值 v ,向比其大的第一个(如果存在) "下匹配的左界区间[l, r]", 连一条容量为1(>=1即可),收益为(l - v) * 2的边同理,             向比其小的第一个(如果存在) "上匹配的右界区间[l, r]", 连一条容量为1(>=1即可),收益为(v - r) * 2的边<此处最多m * 2条边>这个图最终形成啦。层次包括六层{源点0}、{插入层[1, m]}、{下匹配左界区间层[m + 1, m + n)}、{上匹配右界区间层[m + n + 1, m + n + n)}、{真实区间层[m + n + n + 0, m + n + n + n]}、{汇点m + n * 3 + 1}点数总共2 + m + (n - 1) + (n - 1) + (n + 1)共计m + n * 3 + 1个,即点数为4n级别同时,边数可以由(1)~(7)求和可得,为10n级别因而,算法的复杂度为O(点数为4n边数为10n的费用流复杂度) :p【数据】
62 3
5 11
10 3 14 1
1 2 3 4
54 2
1 2 3 4
5 64 5
1 2 3 4
5 6 7 8 94 4
10 50 3 6
1 9 23 54 2
10 50 3 6
9 23*/

2019 ICPC 上海 M Blood Pressure Game [血压游戏] 出题人原封不动标程相关推荐

  1. 2019 ICPC 上海网络赛 K. Peekaboo

    题目连接:https://nanti.jisuanke.com/t/41421 题意:给定三个整数a, b, c,求半径为a.b,圆心坐标为原点的两个同心圆上的整点间的距离为c的点对 题解:推一下圆上 ...

  2. 2019 ACM - ICPC 上海网络赛 E. Counting Sequences II (指数型生成函数)

    繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我写的超高质量题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数(1∼51 \sim 51∼5),以模板题难度 11 ...

  3. The 2019 ICPC Asia Shanghai Regional Contest

    The 2019 ICPC Asia Shanghai Regional Contest 题号 题目 知识点 A Mr. Panda and Dominoes B Prefix Code C Maze ...

  4. 2019 ICPC全国邀请赛(西安)I. Cracking Password(序列检验,BSGS,细节题)

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 2019 ICPC全国邀请赛(西安)I. Cracking Password Weblink http ...

  5. 2019 ICPC Asia Yinchuan Regional(9 / 13)

    2019 ICPC Asia Yinchuan Regional A - Girls Band Party(分组背包) 每个物品有两个标签,名字,颜色,当名字是设置为奖赏时会对整体加上0.1 的贡献, ...

  6. 2019 ICPC World Finals Problem J. Miniature Golf

    2019 ICPC World Finals Problem J. Miniature Golf Solution 设lll为l0l_0l0​时iii的总分为si,l0s_{i,l_0}si,l0​​ ...

  7. 2019 ICPC World Finals Problem B. Beautiful Bridges

    2019 ICPC World Finals Problem B. Beautiful Bridges Solution 太菜了,sbsbsb题调了一个下午. 首先有一个显然的O(n3)O(n^3)O ...

  8. 2019 ICPC Asia-East Continent Final

    2019 ICPC Asia-East Continent Final 题号 题目 知识点 A City 贪心 B Black and White C Dirichlet kkk-th root D ...

  9. 2019考研上海交通大学823计算机通信网真题回忆

    2019考研上海交通大学823计算机通信网真题回忆 考试感想 真题回忆 一.简答 二.分析 三.计算 四.画图 五.综合 考试感想 23号考完823,一声长叹,留下真题梗概,供后来者观摩. 真题回忆 ...

最新文章

  1. java异常处理好习惯
  2. Java程序员必备:序列化全方位解析
  3. 吴忠军 - 如何理解马云所说的月入两三万,三四万的人最幸福?
  4. mysql重置root密码方法
  5. C++中类和对象的一些注意事项 --- 多态
  6. DP--POJ 2241
  7. 超越java jb51_.net mvc超过了最大请求长度的解决方法
  8. 集群故障处理之处理思路以及健康状态检查(三十二)
  9. 用 js判断 一个数是否是素数(质数)_Javascript 判断一个数是不是素数
  10. 下一步工作应该怎样开展
  11. html基本标记练习钱塘湖春行,《钱塘湖春行》练习题
  12. Linux面试题及答案
  13. 阿里直播平台的架构演进
  14. zh-cn、en-us、zh-tw等表示语言(文化)代码与国家地区对照表
  15. office2020与2016版的不同_Office2016与Office2019,这两个版本有些什么区别
  16. 华为云开发者学堂——学习笔记
  17. 多重背包的优化 二进制/单调队列解析
  18. 分享几个下载免费电子图书的地方ebook
  19. 电脑时钟倒计时_你有很好的时间观念吗——Mamsds Timer一款桌面倒计时工具
  20. p8h61主板升级cpu_如何升级和安装新的CPU或主板(或两者)

热门文章

  1. D26 Scala增强
  2. 自学的程序员和自学的吉他手有很多共同点,你玩过吉他吗?
  3. 和谐 Visio 2016
  4. 「深度研究 」加密游戏:最实用的论文
  5. 微信聊天记录怎么彻底删除无法恢复?如何避免数据泄露
  6. 后台基于elment-dialog展示打卡定位腾讯地图
  7. IT4058A型号单节锂离子电池充电管理
  8. 使用leaflet或者openlayers 3 调用MapServer服务最佳实践完整说明
  9. KiCAD6.00快捷键汇总(用于打印)
  10. 计算机数学英语基础,计算机数学与英语到底影响有多大