2017 ACM/ICPC Asia Regional Shenyang Online 记录
这场比赛全程心态爆炸……
开场脑子秀逗签到题WA了一发。之后0贡献。
前期状态全无 H题想复杂了,写了好久样例过不去。
然后这题还是队友过的……
后期心态炸裂,A题后缀数组理解不深,无法特判k = 1时的情况。
然后也没有心思读题了,心静不下来。
Problem B
$ans = k(n - k + 1)$
#include <bits/stdc++.h> using namespace std; typedef long long LL; LL n, k; int main(){ while (~scanf("%lld%lld", &n, &k)){ printf("%lld\n", k * (n - k + 1)); } return 0; }
Problem C
这道题的题意是爸爸和儿子玩游戏,爸爸想输,而且输给儿子的值一定要最小……
仰望了WKC的博客
还有这种操作……
不停地DFS下去,到达一定时间就退出并输出解。
也就是卡时。
我们先做一遍区间DP,求出区间[l, r]的两个信息:
一个是如果只考虑[l, r],爸爸的得分减去儿子的得分的最大值。
一个是如果只考虑[l, r],爸爸的得分减去儿子的得分的最小值。
哪怕是正的也没关系……
然后就是DFS,三个剪枝,卡时间。
两组数据,3s的时限,1500Ms一组,就过了。
(事实上0Ms就可以过……)
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) #define MP make_pair #define fi first #define se second typedef long long LL; const int N = 105; int mn[N][N], mx[N][N]; int a[N], n; int ans; double st; void init(){ memset(mn, 63, sizeof mn); memset(mx, -63, sizeof mx); rep(i, 1, n + 1) mx[i][i - 1] = mn[i][i - 1] = 0; dec(l, n, 1){ rep(r, l, n){ int ll = l, rr = r, sub = 0; if (a[ll] > a[rr]) sub = a[ll++]; else sub = a[rr--]; mx[l][r] = max(mx[l][r], a[ll] + mx[ll + 1][rr] - sub); mn[l][r] = min(mn[l][r], a[ll] + mn[ll + 1][rr] - sub); mx[l][r] = max(mx[l][r], a[rr] + mx[ll][rr - 1] - sub); mn[l][r] = min(mn[l][r], a[rr] + mn[ll][rr - 1] - sub); } } } void dfs(int l, int r, int cnt){ if (clock() - st > 1400) return; if (l > r){ ans = max(ans, cnt); return; } if (cnt + mn[l][r] >= 0) return; if (cnt + mx[l][r] <= ans) return; if (cnt + mx[l][r] < 0){ ans = max(ans, cnt + mx[l][r]); return; } int sub = 0; if (a[l] >= a[r]) sub = a[l++]; else sub = a[r--]; dfs(l + 1, r, cnt + a[l] - sub); dfs(l, r - 1, cnt + a[r] - sub); } int main(){ while (~scanf("%d", &n)){ rep(i, 1, n) scanf("%d", a + i); init(); st = clock(); ans = -(1 << 30); dfs(1, n, 0); if (ans == -(1 << 30)) puts("The child will be unhappy..."); else printf("%d\n", -ans); } return 0; }
Problem D
对原序列求一遍最长不下降子序列,计长度为x
同时求一遍最长不上升子序列,计长度为y
则 $max(x, y) + k >= n$ 时符合题意。
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) #define MP make_pair #define fi first #define se second typedef long long LL; const int N = 1e5 + 10; int n, k; int a[N], c[N], f[N]; int T, ans; void update(int x, int val){ for (; x <= 100001; x += x & -x) c[x] = max(c[x], val); } int query(int x){ int ret = 0; for (; x; x -= x & -x) ret = max(ret, c[x]); return ret; } int main(){ scanf("%d", &T); while (T--){ scanf("%d%d", &n, &k); rep(i, 1, n) scanf("%d", a + i); memset(c, 0, sizeof c); memset(f, 0, sizeof f); rep(i, 1, n){ f[i] = query(a[i]) + 1; update(a[i], f[i]); } ans = 0 ; rep(i, 1, n) ans = max(ans, f[i]); memset(f, 0, sizeof f); memset(c, 0, sizeof c); dec(i, n, 1){ f[i] = query(a[i]) + 1; update(a[i], f[i]); } rep(i, 1, n) ans = max(ans, f[i]); if (ans + k >= n) puts("A is a magic array."); else puts("A is not a magic array."); } return 0; }
Problem E
$ans = F(2x + 3) - 1$
其中F()为斐波那契数列
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) typedef long long LL; const LL mod = 998244353; struct Matrix{ LL arr[3][3];} mul, num, unit; LL k; Matrix Mul(Matrix a, Matrix b){ Matrix c; rep(i, 1, 2) rep(j, 1, 2){ c.arr[i][j] = 0; rep(k, 1, 2) (c.arr[i][j] += (a.arr[i][k] * b.arr[k][j] % mod)) %= mod; } return c; } Matrix Pow(Matrix a, LL k){ Matrix ret(unit); for (; k; k >>= 1, a = Mul(a, a)) if (k & 1) ret = Mul(ret, a); return ret; } int main(){ mul.arr[1][1] = 1; mul.arr[1][2] = 1; mul.arr[2][1] = 1; mul.arr[2][2] = 0; rep(i, 1, 2) unit.arr[i][i] = 1; num.arr[1][1] = 1; num.arr[2][1] = 1; while (~scanf("%lld", &k)){ Matrix P = Pow(mul, 2 * k + 1); Matrix c = Mul(P, num); printf("%lld\n", (c.arr[1][1] + mod - 1) % mod); } return 0; }
Problem G
叉姐的套路果然强大
首先固定一棵生成树,每一条边的权值都是1
加边的过程相当于原来这棵树上这两个点之间的所有边权值变成0。
询问相当于在两点之间的路径上有多少1
很容易想到用树链剖分来实现。然而这题卡了树链剖分。
然后就学习了一下叉姐的套路。
用一个并查集维护某个点上面的离他最近的边权为1的边。
因为总共只有n - 1条边。所以最多只要操作n - 1次。操作的时候暴力往上合并。
计数的话用一个树状数组就可以了。
时间复杂度$O(nlogn)$
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) #define MP make_pair #define fi first #define se second typedef long long LL; typedef pair <int, int> PII; const int N = 1e5 + 10; int s[N], son[N], deep[N], father[N], sz[N], top[N], l[N], r[N]; int q, tot, n; int T; int ti; int m; int f[N]; int ca = 0; vector <int> v[N]; vector <PII> edge; int getfather(int x){ return f[x] == x ? x : f[x] = getfather(f[x]); } void Merge(int x, int y){ int fx = getfather(x); int fy = getfather(y); if (fx == fy) return; f[fx] = fy; } void dfs1(int x, int fa, int dep){ l[x] = ++ti; deep[x] = dep; father[x] = fa; son[x] = 0; sz[x] = 1; for (auto u : v[x]){ if (u == fa) continue; dfs1(u, x, dep + 1); sz[x] += sz[u]; if (sz[son[x]] < sz[u]) son[x] = u; } r[x] = ti; } void dfs2(int x, int tp){ top[x] = tp; if (son[x]) dfs2(son[x], tp); for (auto u : v[x]){ if (u == father[x] || u == son[x]) continue; dfs2(u, u); } } int c[N]; void add(int x, int val){ for (; x <= n; x += x & -x) c[x] += val; } void update(int l, int r, int d){ add(l, d); add(r + 1, -d); } int query(int x){ int ret = 0; for (; x; x -= x & -x) ret += c[x]; return ret; } int LCA(int x, int y){ for (; top[x] ^ top[y]; ){ if (deep[top[x]] < deep[top[y]]) swap(x, y); x = father[top[x]]; } return deep[x] > deep[y] ? y : x; } void work(int x, int y){ x = getfather(x); y = getfather(y); int z = getfather(LCA(x, y)); while (getfather(x) != getfather(z)){ update(l[getfather(x)], r[getfather(x)], 1); Merge(getfather(x), father[getfather(x)]); } while (getfather(y) != getfather(z)){ update(l[getfather(y)], r[getfather(y)], 1); Merge(getfather(y), father[getfather(y)]); } } int main(){ scanf("%d", &T); while (T--){ scanf("%d%d", &n, &m); rep(i, 0, n + 1) v[i].clear(); rep(i, 1, n) f[i] = i; edge.clear(); rep(i, 1, m){ int x, y; scanf("%d%d", &x, &y); int fa = getfather(x); int fb = getfather(y); if (fa != fb){ v[x].push_back(y); v[y].push_back(x); f[fa] = fb; } else edge.push_back(MP(x, y)); } tot = 0; ti = 0; dfs1(1, 0, 0); dfs2(1, 1); memset(c, 0, sizeof c); rep(i, 1, n) f[i] = i; for (auto cnt : edge) work(cnt.fi, cnt.se); printf("Case #%d:\n", ++ca); scanf("%d", &q); while (q--){ int op; scanf("%d", &op); if (op == 1){ int x, y; scanf("%d%d", &x, &y); work(x, y); } else{ int x, y; scanf("%d%d", &x, &y); int z = LCA(x, y); printf("%d\n", deep[x] + deep[y] - deep[z] * 2 - (query(l[x]) + query(l[y]) - 2 * query(l[z]))); } } } return 0; }
Problem H
体现我低下的智商的一道题……
这题还是走了不少弯路(看来我对树型DP的理解还不深入)
我的方法是考虑两种情况。
第一种情况是从某个点买入然后在以他为根的子树中的某个结点卖出。
或者从某个点卖出然后在以他为根的子树中的某个结点买入。
设这个点为x,以他为根的子树中的某个结点为y。
则收益1 = $s[x] - s[y] + a[y] - a[x] = (s[x] - a[x]) - (s[y] - a[y])$
收益2 = $s[x] - s[y] + a[x] - a[y] = (s[x] + a[x]) - (s[y] + a[y])$
其中s[i]为根结点到i的路程长度。
对某个x,要使收益1最大,则$(s[y] - a[y])$要最小。
对某个x,要使收益2最大,则$(s[y] + a[y])$要最小。
那么我们预处理出每个点这两个值,建立ST表,
然后对每个点求出前序DFS序和后序DFS序,
区间查询最小值。
那么第一种情况就解决了。
第二种情况,枚举每个点,求LCA为这个点的两个点之间的最优解。
树型DP就可以了。
以上是我在比赛的时候的想法(真的太麻烦了)
Code如下(比赛的时候还没写完,赛后稍稍fix了下就过了)
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) #define MP make_pair #define fi first #define se second typedef long long LL; typedef pair<int, int> PII; const int N = 1e5 + 10; const int A = 19; int T, n; int ti, ans; int f[N][A], g[N][A], a[N], c[N], d[N], F[N], G[N], s[N], l[N], r[N], fl[N]; vector <PII> v[N]; void dfs(int x, int fa, int now){ s[x] = now; l[x] = ++ti; for (auto cnt : v[x]){ int u = cnt.fi, w = cnt.se; if (u == fa) continue; dfs(u, x, now + w); } r[x] = ti; } void ST(){ rep(i, 1, n) f[i][0] = c[fl[i]]; rep(j, 1, 18) rep(i, 1, n) if ((i + (1 << j) - 1) <= n) f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); rep(i, 1, n) g[i][0] = d[fl[i]]; rep(j, 1, 18) rep(i, 1, n) if ((i + (1 << j) - 1) <= n) g[i][j] = min(g[i][j - 1], g[i + (1 << (j - 1))][j - 1]); } inline int solve_f(int l, int r){ int k = (int)log2((double)(r - l + 1)); return min(f[l][k], f[r - (1 << k) + 1][k]); } inline int solve_g(int l, int r){ int k = (int)log2((double)(r - l + 1)); return min(g[l][k], g[r - (1 << k) + 1][k]); } void ask(int x){ ans = max(ans, c[x] - solve_f(l[x], r[x])); ans = max(ans, d[x] - solve_g(l[x], r[x])); } void dfs2(int x, int fa){ F[x] = c[x]; G[x] = d[x]; for (auto cnt : v[x]){ int u = cnt.fi; if (u == fa) continue; dfs2(u, x); F[x] = min(F[x], F[u]); G[x] = min(G[x], G[u]); } } void dp(int x, int fa){ int m1 = 1 << 30; int m2 = 1 << 30; int yy = 0; for (auto cnt : v[x]){ int u = cnt.fi; if (u == fa) continue; if (yy){ ans = max(ans, 2 * s[x] - F[u] - m2); ans = max(ans, 2 * s[x] - G[u] - m1); } dp(u, x); m1 = min(m1, F[u]); m2 = min(m2, G[u]); yy = 1; } } int main(){ scanf("%d", &T); while (T--){ scanf("%d", &n); rep(i, 0, n + 1) v[i].clear(); rep(i, 1, n) scanf("%d", a + i); rep(i, 2, n){ int x, y, z; scanf("%d%d%d", &x, &y, &z); v[x].push_back(MP(y, z)); v[y].push_back(MP(x, z)); } memset(s, 0, sizeof s); memset(l, 0, sizeof l); ti = 0; dfs(1, 0, 0); rep(i, 1, n) c[i] = s[i] + a[i]; rep(i, 1, n) d[i] = s[i] - a[i]; rep(i, 1, n) fl[l[i]] = i; ST(); ans = 0; rep(i, 1, n) ask(i); memset(F, 0, sizeof F); memset(G, 0, sizeof G); dfs2(1, 0); dp(1, 0); printf("%d\n", ans); } return 0; }
赛后想想,其实根本不需要ST表……
直接来个预处理,第一种情况就解决了
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) #define MP make_pair #define fi first #define se second typedef long long LL; typedef pair<int, int> PII; const int N = 1e5 + 10; const int A = 19; int T, n; int ti, ans; int f[N][A], g[N][A], a[N], c[N], d[N], F[N], G[N], s[N], l[N], r[N], fl[N]; vector <PII> v[N]; void dfs(int x, int fa, int now){ s[x] = now; l[x] = ++ti; for (auto cnt : v[x]){ int u = cnt.fi, w = cnt.se; if (u == fa) continue; dfs(u, x, now + w); } r[x] = ti; } void ST(){ rep(i, 1, n) f[i][0] = c[fl[i]]; rep(j, 1, 18) rep(i, 1, n) if ((i + (1 << j) - 1) <= n) f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); rep(i, 1, n) g[i][0] = d[fl[i]]; rep(j, 1, 18) rep(i, 1, n) if ((i + (1 << j) - 1) <= n) g[i][j] = min(g[i][j - 1], g[i + (1 << (j - 1))][j - 1]); } inline int solve_f(int l, int r){ int k = (int)log2((double)(r - l + 1)); return min(f[l][k], f[r - (1 << k) + 1][k]); } inline int solve_g(int l, int r){ int k = (int)log2((double)(r - l + 1)); return min(g[l][k], g[r - (1 << k) + 1][k]); } void ask(int x){ ans = max(ans, c[x] - solve_f(l[x], r[x])); ans = max(ans, d[x] - solve_g(l[x], r[x])); } void dfs2(int x, int fa){ F[x] = c[x]; G[x] = d[x]; for (auto cnt : v[x]){ int u = cnt.fi; if (u == fa) continue; dfs2(u, x); F[x] = min(F[x], F[u]); G[x] = min(G[x], G[u]); } } void dp(int x, int fa){ int m1 = 1 << 30; int m2 = 1 << 30; int yy = 0; for (auto cnt : v[x]){ int u = cnt.fi; if (u == fa) continue; if (yy){ ans = max(ans, 2 * s[x] - F[u] - m2); ans = max(ans, 2 * s[x] - G[u] - m1); } dp(u, x); m1 = min(m1, F[u]); m2 = min(m2, G[u]); yy = 1; } } int main(){ scanf("%d", &T); while (T--){ scanf("%d", &n); rep(i, 0, n + 1) v[i].clear(); rep(i, 1, n) scanf("%d", a + i); rep(i, 2, n){ int x, y, z; scanf("%d%d%d", &x, &y, &z); v[x].push_back(MP(y, z)); v[y].push_back(MP(x, z)); } memset(s, 0, sizeof s); memset(l, 0, sizeof l); ti = 0; dfs(1, 0, 0); rep(i, 1, n) c[i] = s[i] + a[i]; rep(i, 1, n) d[i] = s[i] - a[i]; rep(i, 1, n) fl[l[i]] = i; ST(); ans = 0; rep(i, 1, n) ask(i); memset(F, 0, sizeof F); memset(G, 0, sizeof G); dfs2(1, 0); dp(1, 0); printf("%d\n", ans); } return 0; }
Problem J
最后这题完全读不进去啊……
题目大意就是给出一棵树,和若干条路径。
由于某些点不可经过,这些给出的路径是走不通的。
也就是说如果他给出了路径(u, v),那么u就不能走到v,反过来也一样。
求不可经过的点最少有几个。
这道题要用到一个结论:
如果树上两条路径有交集,
假设u1和u2分别为两条路径两个端点的LCA,
一定存在u1属于这个交集,或者u2属于这个交集。
对于路径(u, v),我们求出u和v的LCA——z。
然后对这p条路径,我们以deep[z]为关键字排序。
deep[z]越大,排越前面。
接下来就是一个贪心的过程。
我们从前往后处理这些路径,
如果当前这条路径的u或v已经被标记,那么直接跳过。
如果当前这条路径的u或v都没有被标记,
那么我们标记以z为根的子树的所有点。
然后对答案加1.
之前我们预处理出所有点的前序DFS序l[i]和后序DFS序r[i],
若要标记以i为根的子树的所有点,那么给区间l[i]到r[i]打上标记即可。
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) const int N = 1e4 + 10; const int A = 18; int deep[N], c[N], l[N], r[N], sz[N], son[N], top[N], father[N]; int n, m, ti, ans; vector <int> v[N]; struct node{ int x, y, z; friend bool operator < (const node &a, const node &b){ return deep[a.z] > deep[b.z]; } } path[N * 5]; void dfs(int x, int fa, int dep){ father[x] = fa; deep[x] = dep; l[x] = ++ti; sz[x] = 1; for (auto u : v[x]){ if (u == fa) continue; dfs(u, x, dep + 1); sz[x] += sz[u]; if (sz[son[x]] < sz[u]) son[x] = u; } r[x] = ti; } void dfs2(int x, int fa, int tp){ top[x] = tp; if (son[x]) dfs2(son[x], x, tp); for (auto u : v[x]){ if (u == son[x] || u == fa) continue; dfs2(u, x, u); } } int LCA(int x, int y){ for (; top[x] ^ top[y]; ){ if (deep[top[x]] < deep[top[y]]) swap(x, y); x = father[top[x]]; } return deep[x] > deep[y] ? y : x; } inline int update(int x, int y){ for (; x <= n; x += x & -x) ++c[x]; ++y; for (; y <= n; y += y & -y) --c[y]; } inline int query(int x){ int ret = 0; for (; x; x -= x & -x) ret += c[x]; return ret; } int main(){ while (~scanf("%d", &n)){ ++n; rep(i, 0, n + 1) v[i].clear(); ti = 0; rep(i, 2, n){ int x, y; scanf("%d%d", &x, &y); ++x; ++y; v[x].push_back(y); v[y].push_back(x); } memset(son, 0, sizeof son); dfs(1, 0, 0); dfs2(1, 0, 1); scanf("%d", &m); rep(i, 1, m){ int x, y, z; scanf("%d%d", &x, &y); ++x; ++y; z = LCA(x, y); path[i] = node{x, y, z}; } sort(path + 1, path + m + 1); memset(c, 0, sizeof c); ans = 0; rep(i, 1, m){ int x = query(l[path[i].x]), y = query(l[path[i].y]); if (x || y) continue; update(l[path[i].z], r[path[i].z]); ++ans; } printf("%d\n", ans); } return 0; }
Problem L
从第1个点扫过去,扫到第x1个点的时候游戏终止。
于是我们从x1+ 1接着扫,扫到游戏终止时的那个点x2
继续从x2 + 1开始扫。
周而复始,直到扫完为止。扫完一遍更新一次答案。
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) const int N = 1e6 + 10; int n, l, r, ret, ans; int a[N], b[N], c[N << 1], f[N << 1]; int main(){ while (~scanf("%d", &n)){ rep(i, 1, n) scanf("%d", a + i); rep(i, 1, n) scanf("%d", b + i); rep(i, 1, n) c[i] = a[i] - b[i]; rep(i, 1, n) c[i + n] = c[i]; rep(i, 1, n << 1) f[i] = f[i - 1] + c[i]; l = 1; r = 1; c[1] = 0; rep(i, 1, n) c[i] = c[i - 1] + a[i]; rep(i, 1, n) c[n + i] = c[n + i - 1] + a[i]; ret = 0; while (true){ while (r < 2 * n && r - l + 1 < n && f[r] - f[l - 1] >= 0) ++r; if (c[r] - c[l - 1] > ret){ ret = c[r] - c[l - 1]; ans = l - 1; } ++r; l = r; if (l > 2 * n) break; } printf("%d\n", ans); } return 0; }