血压游戏,实质名归好吧233333~ 代码时间是 2019-11-23 18:34:27一个标点符号我都没有修改,贴在这里了。 不排除有错误,我也真的没有再检查过了。询问了出题组包括bin巨,他们同意我把标程发过来。 我觉得很有可能会被发现哪里写得有问题,但是能学到知识不是美滋滋吗 ~ 而且确实也没有影响比赛结果。问心无愧。 // #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 + 1 Ele 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) down pair<int, int>vaR[N]; // sorted R list (vaR, id) up int pos[N]; // a to b, record the ans vector<int>ansVec; // ans vector void 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 pos FMS(pos, 0, n + 2); int LpreID = 0; sort(LbTOa.begin(), LbTOa.end(), cmpL); // vaL is going down after sorting for(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 sorting for(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 output ansVec.clear(); for (int y = 0; y <= n; ++y) { if (y) { ansVec.push_back(a[y]); } // possibility 1: matched already if (y != 0 && y != n && pos[y]) { ansVec.push_back(pos[y]); continue; } // possibility 2: head or tail bool 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 elements if(!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 + 2 vector<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 check set<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: fastNetworkFlow bool NplusM_MCMF = true; vector<LL> fastMCMFvec; if (NplusM_MCMF) { mcmf.NplusM_mapBuild(); fastMCMFvec = mcmf.fastMCMF(basic); printVec("", fastMCMFvec); //fastMCMFvec mcmf.NplusM_output(); FMS(mcmf.first, 0, mcmf.ED + 2); // ans checker string 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: slowNetworkFlow bool NmultM_MCMF = false; vector<LL> slowMCMFvec; if (NmultM_MCMF) { mcmf.NmultM_mapBuild(); slowMCMFvec = mcmf.slowMCMF(basic); printVec("", slowMCMFvec); // slowMCMFvec mcmf.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】 Input 1 4 5 21 3 48 39 16 66 9 64 36 Output 36 21 66 3 64 48 9 39 16 Input 1 4 4 10 50 3 6 1 9 23 5 Output 150 5 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 【数据】 6 2 3 5 11 10 3 1 4 1 1 2 3 4 5 4 2 1 2 3 4 5 6 4 5 1 2 3 4 5 6 7 8 9 4 4 10 50 3 6 1 9 23 5 4 2 10 50 3 6 9 23 */
来源:CSDN
作者:snowy_smile
链接:https://blog.csdn.net/snowy_smile/article/details/103245022