The 2018 ACM-ICPC Asia Qingdao Regional Contest I.Soldier Game
题目链接
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4066
题目大意
你需要把 $n$ 个士兵分成若干组,满足每组都有 $1$ 个或 $2$ 个士兵,且要保证士兵编号连续。求权值最大的组的权值减去权值最小的组的权值为多少。
题目分析
题目的意思为,用长度为 $1$ 和 $2$ 的线段不重叠覆盖序列,使得线段中最大值减去最小值最小。如果最小值能够固定,那么题目的做法可以类比最小生成树的做法,将所有线段按权值大小由小到大依次添加进去,维护是否可以完整覆盖。对于本体来说,可以先将所有 $n$ 个长度为 $1$ 的线段和 $n – 1$ 长度为 $2$ 的线段先提取出来,按照权值有效到大排序,通过枚举最小值,维护最小的最大值即可。在这个过程中,需要维护是否能完整覆盖。可以将为每个线段 $(l, r)$ 变成 $l$ 指向 $r + 1$ 的一条有向边,维护 $1$ 与 $n + 1$ 的连通性即可。由于枚举最小值时会有修改和删除操作,需要数据结构来维护。可以使用线段树维护区间的连通性,合并时考虑中间时直接连接还是跳一个连接。枚举所有可行的最小值,维护对应的最大值,对所有结果取最小值即为答案
代码
#include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <string> #include <algorithm> #include <map> using namespace std; const int maxn = 2e5 + 5; int n, m, t; pair <long long, pair <int, int> > f[maxn]; long long a[maxn]; int b[maxn][2]; struct node { int l, r; int lc, rc; bool v[2][2]; }; struct st { node tree[maxn]; int root; int tc; int merge(node &ret, node x, node y){ if((x.v[0][0] and y.v[0][0] and b[x.r][0]) or (x.v[0][1] and y.v[0][0] and b[x.r - 1][1]) or (x.v[0][0] and y.v[1][0] and b[x.r][1])){ ret.v[0][0] = true; }else{ ret.v[0][0] = false; } if((x.v[1][0] and y.v[0][0] and b[x.r][0]) or (x.v[1][1] and y.v[0][0] and b[x.r - 1][1]) or (x.v[1][0] and y.v[1][0] and b[x.r][1])){ ret.v[1][0] = true; }else{ ret.v[1][0] = false; } if((x.v[0][0] and y.v[0][1] and b[x.r][0]) or (x.v[0][1] and y.v[0][1] and b[x.r - 1][1]) or (x.v[0][0] and y.v[1][1] and b[x.r][1])){ ret.v[0][1] = true; }else{ ret.v[0][1] = false; } if((x.v[1][0] and y.v[0][1] and b[x.r][0]) or (x.v[1][1] and y.v[0][1] and b[x.r - 1][1]) or (x.v[1][0] and y.v[1][1] and b[x.r][1])){ ret.v[1][1] = true; }else{ ret.v[1][1] = false; } if(x.l == x.r){ ret.v[1][0] = y.v[0][0]; if(y.r - y.l == 1){ ret.v[1][1] = true; } } if(y.l == y.r){ ret.v[0][1] = x.v[0][0]; if(x.r - x.l == 1){ ret.v[1][1] = true; } } return 0; } int update_tree(int pos, int l, int r){ int mid = (tree[pos].l + tree[pos].r) / 2; if(tree[pos].l != tree[pos].r){ if(r <= mid){ update_tree(tree[pos].lc, l, r); }else if(l <= mid and mid < r){ update_tree(tree[pos].lc, l, mid); update_tree(tree[pos].rc, mid + 1, r); }else{ update_tree(tree[pos].rc, l, r); } merge(tree[pos], tree[tree[pos].lc], tree[tree[pos].rc]); } return 0; } int init_tree(int l, int r){ int mid = (l + r) / 2; int pos = ++tc; tree[pos].l = l; tree[pos].r = r; memset(tree[pos].v, 0, sizeof(tree[pos].v)); if(l == r){ tree[pos].v[0][0] = true; }else{ tree[pos].lc = init_tree(l, mid); tree[pos].rc = init_tree(mid + 1, r); } return pos; } bool query(){ return tree[root].v[0][0]; } int update(int x, int y){ update_tree(root, x, y); return 0; } int init(){ tc = 0; root = init_tree(1, n + 1); return 0; } }; st tree; int read(){ int x = 0; int flag = 1; char ch = getchar(); while('0' > ch or ch > '9'){ if(ch == '-'){ flag = -1; } ch = getchar(); } while('0' <= ch and ch <= '9'){ x = x * 10 + ch - '0'; ch = getchar(); } x *= flag; return x; } int solve(){ int i, j; int x, y; long long ans = 1e15; int cnt = 0; for(i=1;i<=n;i++){ f[i].first = a[i]; f[i].second.first = f[i].second.second = i; } for(i=1;i<n;i++){ f[n + i].first = a[i] + a[i + 1]; f[n + i].second.first = i; f[n + i].second.second = i + 1; } sort(f + 1, f + 2 * n); for(i=1;i<=n;i++){ b[i][0] = b[i][1] = false; } tree.init(); j = 0; while(!tree.query()){ j++; x = f[j].second.first; y = f[j].second.second; b[x][y - x] = true; tree.update(x, y + 1); } for(i=1;i<2*n;i++){ ans = min(ans, f[j].first - f[i].first); x = f[i].second.first; y = f[i].second.second; b[x][y - x] = false; tree.update(x, y + 1); while(!tree.query()){ j++; if(j == 2 * n){ printf("%lld\n", ans); return 0; } x = f[j].second.first; y = f[j].second.second; b[x][y - x] = true; tree.update(x, y + 1); } } return 0; } int main(){ int i, j; t = read(); while(t--){ n = read(); for(i=1;i<=n;i++){ a[i] = read(); } if(n == 1){ printf("0\n"); }else{ solve(); } } return 0; }