蒟蒻的第二次 ARC
link
result
只能说我太弱了(差一点就掉 rating),但有幸和 tourist 打同场还是比较开心,发现那些人手速都好快啊,有个人和我做的题一样,但 19:12 就打完了(此时我一道题都还没做)
During 一进去, A 题比我想象的难(虽然其实很简单),思路大概就是把这个数和反过来的数分别加 $0$ 直到比 $n$ 大,但几个细节都没想清楚, WA 了三发就去看 B 了
B 感觉比 A 简单,因为发现只有周围是 $8$ 个全满的才有可能不满足(其它位置周围都是奇数),看样例就发现只要上一行比下一行大,然后本行中一大一小交替即可
AC 了 B 以后同机房有人过了 A ,他告诉我如果原数 $x$ 反转后 $x’ < x$ 就没有方案,因为原题中那个函数定义的是最小的,我仔细一看好像是的,就判了一下,结果还是 WA ,就一边问一边看 C ,在 C 有个大概思路的时候发现了 A 的问题:我求原数反转求错了!直接改掉过了 A (吃了 $5$ 发罚时我也是醉了)
C 我的大概思路就是直接问每个点到 $1, 2$ 的距离,如果 $1, 2$ 没有直接相连那答案就是 $\min(dis(u, 1) + dis(u, 2))$ ;如果每个 $u$ 都满足 $\mid dis(u, 1) - dis(u, 2) \mid == 1$ 我就认为 $1, 2$ 直接相连,因为 A 的罚时有点多, 我没多想就交了,一看, WA 一个点!蒙都知道是 $1, 2$ 直接相连判断错了,然后仔细一想,自己 hack 了自己:
上图的情况下我的思路会判断成 $1, 2$ 直接相连,并且发现能 hack 我的情况只有上面那个 $1, 2$ 中间两个点(那两个点上可能还有点),并且由于我上面只问了 $2n - 4$ 次,就直接找到这两个点,然后问它们的距离,如果为 $1$ 就是 hack 的情况,输出 $3$ ,最离谱的是期间因为交互题没 fflush(stdout)
TLE 了两发(血亏),不管怎样终于 AC 了
这里同机房的 l18q 巨佬有个很好的思路: $\mid dis(u, 1) - dis(u, 2) \mid$ 和 $dis(u, 1) + dis(u, 2)$ 中必有一个是 $1, 2$ 之间的距离,所以他也询问 $2n - 4$ 次,统计次数,出现恰好一半的就是答案,但特殊情况有两个,他只考虑到了前者,所以考场上挂的有点多:
上面就是两种特殊情况,第一种直接判断如果有点 $dis(u, 1) == dis(u, 2)$ 那答案就是两个相加;第二种有点麻烦,因为统计出来距离为 $1, 2$ 各占一半,解决方法就是随便取两个相对点问一下距离,如果是 $3$ 答案就是 $1$
搞完 C 去看了一眼榜, 第一不是 tourist 把我惊呆了 ,发现大部分人都过了 ABC 在搞 DEF ,感觉搞出一道排名就会很好看 这不废话 ,虽然 D 过的人多,但我感觉没看懂题就去看 E ,想了半天决定贪心 + 退火乱搞,几乎卡着时间打(开打的时候只有 $20min$ 了)完改了几次参数没退过去
After 隔了一天来改题
D 认真读了下题,理清了题意,有点后悔考场上去打 E ,但也就一点点,因为这题也好难啊
首先考虑可以进行无限多次操作,我们考虑一条路径,它的所有顶点中只有一个端点上没有棋子,那么显然这些棋子可以在该路径上一直来回,无限多次的条件就被满足了,但唯一性还没满足,不唯一显然是一个路径上有点走到外面去了,那么该路径有棋子的某个点连向某条路径有棋子的端点(这样那个端点走后就给这个点留出位置)
于是考虑树形 dp 求出将树划分成若干条上述路径的方案数,显然这就是答案,问题在于状态和转移,我们称有棋子的端点为“占”,无其中的端点为“空”,发现一共有 $5$ 中情况:
该点所在路径的占在上面,空在该点或其子树内
该点所在路径的空在上面,占在该点或其子树内
该点就是占,空在子树内
该点就是空,占在子树内
占、空都在子树内
那么设其为 $d[x, 0/1/2/3/4]$ ,显然转移为 $y$ 是 $x$ 的儿子, $y’, y’’$ 是任意一个 $y$ : $$ \begin{aligned} & d[x, 0] += \prod d[y, 2] \\ & d[x, 0] += d[y’, 0] * \prod d[y, 4] \\ & d[x, 1] += \prod d[y, 3] \\ & d[x, 1] += d[y’, 1] * \prod d[y, 4] \\ & d[x, 2] += d[y’, 0] * \prod d[y, 3] \\ & d[x, 3] += d[y’, 1] * \prod d[y, 2] \\ & d[x, 4] += d[y’, 0] * d[y’, 1] * \prod d[y, 4] \\ \end{aligned} $$ 发现 $1$ 和 $2$ 、 $3$ 和 $4$ 是对称的,放案数相同,把它们放在一起 dp ,注意合并 $1, 2$ 后 $4$ 计算完后要乘二,因为可以交换,另外,写的时候当然不是考虑哪个儿子做 $y’, y’’$ 而是枚举到每个儿子时,讨论它已经选好了 $y’, y’’$ 和选当前儿子做 $y’, y’’$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <bits/stdc++.h> using LL = long long ;const int N = 2e5 + 100 , P = 998244353 ;struct Edge { int ne, ver; } e[N << 1 ];int h[N], idx = 0 , n;void add (int x, int y) { e[idx] = {h[x], y}, h[x] = idx++; }void adj (int &x) { x += (x >> 31 ) & P; }std::array<int , 3> dp (int x, int fa) { std::array<int , 3> d, f; d = {0 , 0 , 0 }, f = {0 , 1 , 1 }; for (int i = h[x], y; ~i; i = e[i].ne) if ((y = e[i].ver) != fa) { auto dy = dp (y, x); d[0 ] = LL (d[0 ]) * dy[2 ] % P; adj (d[0 ] += LL (f[2 ]) * dy[0 ] % P - P); d[1 ] = LL (d[1 ]) * dy[1 ] % P; adj (d[1 ] += LL (f[1 ]) * dy[0 ] % P - P); d[2 ] = LL (d[2 ]) * dy[2 ] % P; adj (d[2 ] += LL (f[0 ]) * dy[0 ] % P - P); f[0 ] = LL (f[0 ]) * dy[2 ] % P; adj (f[0 ] += LL (f[2 ]) * dy[0 ] % P - P); f[1 ] = LL (f[1 ]) * dy[1 ] % P; f[2 ] = LL (f[2 ]) * dy[2 ] % P; } adj (d[0 ] += f[1 ] - P), adj (d[2 ] += d[2 ] - P); return d; } int main () { scanf ("%d" , &n); std::memset (h + 1 , -1 , n << 2 ); for (int i = 1 , u, v; i < n; ++i) { scanf ("%d %d" , &u, &v); add (u, v), add (v, u); } auto ans = dp (1 , -1 ); printf ("%d\n" , (LL (ans[1 ]) * 2 + ans[2 ]) % P); return 0 ; }
E 考场上鬼的退火,没搞成,正解似乎是网络流,个人感觉是最难的,因为那个建图好难想 网络流弱鸡一只
首先对于每一对 $x, y$ ,把 $a_x, a_y$ 加至大于等于 $\min(b_x, b_y)$ 显然这是对的,然后,建图如下:
建立源点、汇点 $S, T$
建立 $n$ 个点代表 $x$ 自己
建立 $n * 100$ 个点 $(y, i)$ 代表 $a_y$ 要 $\ge i$
对于所有 $x$ ,连边 $S \to x$ ,权值为 $b_x - a_x$ ,代表 $x$ 要自己打败自己的怪兽
对于所有询问的 $(x, y)$ ,连边 $x \to (y, b_x -a_y)$ ,权值为 $\infty$ ,代表由 $y$ 打败 $x$ 的怪兽
对于所有 $(y, i)$ 连边 $(y, i) \to (y, i - 1)$ ,权为 $\infty$ ,代表满足 $(y, i)$ 就满足 $(y, i - 1)$
对于所有 $(y, i)$ 连边 $(y, i) \to T$ 权值为 $1$
当然,上面所有建立的边、点都是在该怪兽还没被打表的前提下,所以不会有负权边
考虑时间,记 $A$ 为 $a, b$ 的最大值,最大流量是 $O(nA)$ ,而边数是 $O(n^2 + nA)$ ,所以复杂度是 $O(n^3A + n^2A^2)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <bits/stdc++.h> const int N = 100 + 50 , A = 100 + 50 , INF = 0x3f3f3f3f ;int n, m, ans = 0 , a[N], b[N];bool mp[N][N];int S, T, h[N + A * N], idx = 0 , q[N + A * N], hh, tt, dep[N + A * N], cur[N + A * N];struct Edge { int ne, ver, w; } e[(N * N + N * A) << 1 ];void add (int x, int y, int z) { e[idx] = {h[x], y, z}, h[x] = idx++; }void dad (int x, int y, int z) { add (x, y, z), add (y, x, 0 ); }int id (int x, int i) { return n + 100 * (x - 1 ) + i; }bool bfs () { hh = tt = 1 ; std::memset (dep, 0 , (T + 1 ) * sizeof (int )); q[dep[S] = 1 ] = S, cur[S] = h[S]; while (hh <= tt) { int x = q[hh++]; for (int i = h[x], y; ~i; i = e[i].ne) if (!dep[y = e[i].ver] && e[i].w) { dep[y] = dep[x] + 1 , cur[y] = h[y]; if (y == T) return true ; q[++tt] = y; } } return false ; } int find (int x, int lim) { if (x == T) return lim; int res = 0 ; for (int i = cur[x], y, t; ~i && res < lim; i = e[i].ne) { cur[x] = i; if (dep[y = e[i].ver] == dep[x] + 1 && e[i].w) { t = find (y, std::min (e[i].w, lim - res)); if (!t) dep[y] = 0 ; else e[i].w -= t, e[i ^ 1 ].w += t, res += t; } } return res; } int dinic () { int flow, res = 0 ; while (bfs ()) while (flow = find (S, INF)) res += flow; return res; } int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; ++i) scanf ("%d %d" , &a[i], &b[i]); scanf ("%d" , &m); for (int i = 1 , x, y, z; i <= m; ++i) { scanf ("%d %d" , &x, &y); z = std::min (b[x], b[y]); if (a[x] < z) ans += z - a[x], a[x] = z; if (a[y] < z) ans += z - a[y], a[y] = z; mp[x][y] = mp[y][x] = true ; } S = id (n + 1 , 1 ), T = S + 1 ; std::memset (h, -1 , (T + 1 ) * sizeof (int )); for (int i = 1 ; i <= n; ++i) if (b[i] > a[i]) dad (S, i, b[i] - a[i]); for (int i = 1 ; i <= n; ++i) for (int j = 1 ; j <= n; ++j) if (mp[i][j] && b[i] > a[j]) dad (i, id (j, b[i] - a[j]), INF); for (int i = 1 ; i <= n; ++i) for (int j = 2 ; j <= 100 ; ++j) dad (id (i, j), id (i, j - 1 ), INF); for (int i = 1 ; i <= n; ++i) for (int j = 1 ; j <= 100 ; ++j) dad (id (i, j), T, 1 ); printf ("%d\n" , ans + dinic ()); return 0 ; }
F $O(n^3)$ 的暴力 dp 比较显然,设 $d[i, j, k]$ 表示“前 $i$ 次操作, $X$ 有 $j$ 的魔法值, $Y$ 有 $k$ 的魔法值的最大伤害”,直接 dp 即可
正解的话,发现设 $X$ 进行了 $C_X$ 次攻击,分别是第 $x_1, x_2, …, x_{C_X}$ 次那么其贡献为 $\sum x_i - \frac{(C_X + 1)C_X}{2}$ ,所以,最后答案是 $\sum x_i + \sum y_i - \frac{(C_X + 1)C_X}{2} - \frac{(C_Y + 1)C_Y}{2}$ ,现在,我们把操作分成 $5$ 类,考虑它们的影响, 对于本个 $i$ :
$X, Y$ 的选则已经固定,我们无法改变,直接计算即可
$X, Y$ 的选则都不固定,且相反: $\sum x_i + \sum y_i$ 一定会 $+i$ ,问题是 $C_X, C_Y$ 谁 $+1$
$X, Y$ 的选则都不固定,且相同:要么 $\sum x_i + \sum y_i += 2i$ 且 $C_X, C_Y += 1$ ,要么没有影响
$X$ 的选则固定了:要么 $\sum x_i + \sum y_i += i$ 且 $C_Y += 1$ ,要么没有影响
$Y$ 的选则固定了:要么 $\sum x_i + \sum y_i += i$ 且 $C_X += 1$ ,要么没有影响
直接选显然 sb ,发现当 $1, 2, 3$ 都确定了的时候, $4/5$ 其实可以贪心选,且决策只与 $C_Y/C_X$ 有关,不妨预处理;然后对于 $2$ 枚举有多少个 $C_X + 1$ ,再对于 $3$ 枚举有多少个有影响,最后统计一下答案,总时间 $O(n^2)$ ;需要注意的就是 $1, 4,5$ 中固定的贡献是没有选则必须计算的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <bits/stdc++.h> #define pb push_back const int N = 8000 + 100 ;int n, as, cx1, cy1, ans, cx, cy;std::vector<int > typ[6 ]; int dt3[N], dt4[N], dt5[N];int get (int c) { return (c * (c + 1 )) >> 1 ; }int calc (std::vector<int > &tp, int c) { int res = 0 , t = get (c + 1 ) - get (c); for (int p : tp) if (p > t) { res += p - t; ++c, t = get (c + 1 ) - get (c); } else break ; return res; } int main () { scanf ("%d" , &n), as = cx1 = cy1 = 0 ; for (int i = 1 , a, b, c, d; i <= n; ++i) { scanf ("%d %d %d %d" , &a, &b, &c, &d); if (a == c) { if (a == 2 ) as += i, ++cx1; if (b != d) typ[4 ].pb (i); else if (b == 2 ) as += i, ++cy1; } else { if (b == d) { typ[5 ].pb (i); if (b == 2 ) as += i, ++cy1; } else if (a == b) typ[3 ].pb (i); else typ[2 ].pb (i), as += i; } } for (int i = 2 ; i <= 5 ; ++i) if (typ[i].size ()) std::reverse (typ[i].begin (), typ[i].end ()); for (int i = 0 ; i <= n; ++i) dt4[i] = calc (typ[4 ], i); for (int i = 0 ; i <= n; ++i) dt5[i] = calc (typ[5 ], i); for (int i = 0 ; i < typ[3 ].size (); ++i) dt3[i + 1 ] = dt3[i] + (typ[3 ][i] << 1 ); for (int i = typ[2 ].size (), t; ~i; --i) for (int j = typ[3 ].size (); ~j; --j) { cx = cx1 + i + j, cy = cy1 + typ[2 ].size () - i + j, t = as + dt3[j]; t -= get (cx) + get (cy); t += dt4[cy] + dt5[cx]; ans = std::max (ans, t); } printf ("%d\n" , ans); return 0 ; }