A 吃了发罚时……
赛时进度:ABCDE Accepted,Score:6025,Rank:7,Rating:1987 -> 2189。
改题进度:All Accepted。
A 题意:给定 $n$,拆分成 4 和 6,问最多有多少个 4、最多有多少个 6,或报告无解。
容易发现 $n$ 是奇数或 $n = 2$ 的时候显然无解。剩下的按余数那么随便分讨一下即可。
1 2 3 4 5 6 7 8 9 10 void work () { LL n; scanf ("%lld" , &n); if ((n & 1 ) || n < 4 ) return void (puts ("-1" )); if (n % 6 == 2 ) printf ("%lld " , n / 6 + 1 ); else printf ("%lld " , (n + 2 ) / 6 ); if (n % 4 == 2 ) printf ("%lld\n" , n / 4 ); else printf ("%lld\n" , n / 4 ); }
B 题意:给定一个序列 $n$ 和 $q$ 次操作,单点修改或是整体赋值。问操作后的和。$n\leq 2\times 10 ^ 5, q\leq 2\times 10 ^ 5$。
给出线性做法。
考虑每个数最后被覆盖 / 单点修改的时间,如果单点修改的时间晚于整体赋值的时间,那么就是 $a_i$,否则值就是最后的 $cov$。用增量法维护和即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int main () { std::cin >> n >> Q; for (int i = 1 ; i <= n; ++ i) scanf ("%d" , a + i); for (int i = 1 ; i <= n; ++ i) sum += a[i]; int pos, x, op; for (int cs = 1 ; cs <= Q; ++ cs) { scanf ("%d" , &op); if (op == 1 ) { scanf ("%d %d" , &pos, &x); if (t[pos] < ct) sum += x - cov; else sum += x - a[pos]; t[pos] = cs, a[pos] = x; } else { scanf ("%d" , &x); sum = (LL) n * x; cov = x, ct = cs; } printf ("%lld\n" , sum); } return 0 ; }
C 给出 $n\times n$ 的方格,有些位置有关键点,$q$ 次操作,设立关键点,删除关键点,询问一个子矩阵所有的格子都被关键点所覆盖。覆盖是将一行 / 一列的点覆盖。$n, q\leq 2\times 10 ^ 5$。
考虑结论:想要覆盖一个子矩阵,当且仅当所有行 / 所有列都被覆盖。
于是我们可以将 行 / 列 分开,分别判断 行 / 列 是否分别合法,一个合法就都可以了。
于是随便使用树状数组判断区间是否合法即可。具体的,如果覆盖的时候这个位置覆盖次数为 0,就在这个位置 +1;如果删除后这个位置覆盖次数为 0,就在这个位置 -1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int n, Q, t, x, y, sx, sy; std::cin >> n >> Q; while (Q --) { scanf ("%d %d %d" , &t, &x, &y); if (t == 1 ) row.add (x, !r[x] ++), col.add (y, !c[y] ++); else if (t == 2 ) row.add (x, -(!-- r[x])), col.add (y, -(!-- c[y])); else { scanf ("%d %d" , &sx, &sy); puts ((row.ask (sx) - row.ask (x - 1 )) == sx - x + 1 || (col.ask (sy) - col.ask (y - 1 )) == sy - y + 1 ? "Yes" : "No" ); } } return 0 ; }
D 给出 $n$ 个点 $m$ 条边的有向图,点带权,任选点数为 $k$ 的路径使得最大点权最小。$n, m\leq 2\times 10 ^ 5, k\leq 10 ^ {18}$。
最大最小,果断二分。
二分后,有一些点不再能走,那么转化为了在现有图上走出 $k$ 个点。
首先容易发现如果有环的话,那么任意 $k$ 都是可以的。对于剩下的情况,直接拓扑排序做一下最长路径即可。拓扑排序的时候可以同时判断有没有环。判断时间复杂度线性。
总复杂度 $O((n + m)\log a)$。
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 bool check (int mid) { std::queue<int > q; for (int i = 1 ; i <= n; ++ i) deg[i] = d[i] = 0 ; for (int x = 1 ; x <= n; ++ x) { if (a[x] > mid) continue ; for (int v : g[x]) if (a[v] <= mid) deg[v] ++; } for (int i = 1 ; i <= n; ++ i) if (!deg[i] && a[i] <= mid) q.push (i), d[i] = 1 ; while (!q.empty ()) { int x = q.front (); q.pop (); for (int v : g[x]) if (a[v] <= mid) { chkmax (d[v], d[x] + 1 ); if (!-- deg[v]) q.push (v); } } for (int i = 1 ; i <= n; ++ i) if (deg[i] || d[i] >= k) return true ; return false ; } int main () { std::cin >> n >> m >> k; for (int i = 1 ; i <= n; ++ i) scanf ("%d" , a + i); for (int i = 1 , u, v; i <= m; ++ i) { scanf ("%d %d" , &u, &v); g[u].pb (v); } if (!check (1e9 + 10 )) return puts ("-1" ), 0 ; int l = 0 , r = 1e9 + 10 ; while (l < r) { int mid = (l + r) >> 1 ; if (check (mid)) r = mid; else l = mid + 1 ; } std::cout << l << std::endl; return 0 ; }
E 题意:给定长度为 $n$ 的字符串,满足每一个都属于 $\{a, b, c\dots, p, q\}$ 17 个字符,有一些待填,用 ?
表示,$q$ 次给定可以将 ?
变成的字符的集合,问所有填法回文串个数的和。$n\leq 1000, q\leq 2\times 10 ^ 5$。
容易发现 $n\leq 1000$,我们可以考虑枚举出所有可能的回文串,然后判断是否可能变为回文串。注意我们需要增量枚举,否则是 $O(n ^ 3)$ 的。
考虑现在新加入的两边字符:
两边都是 ?
,这样答案就乘上 $|\sum|$。这样我们还需要枚举 $\sum$ 的大小贡献答案。
有一边是 ?
,这样字符集中需要有另一边的字符。
两边都不是 ?
,这样需要两边字符一样,否则直接停止。
此外,其他的 ?
可以随便选,那么我们需要给出随意选的个数。容易发现贡献和 $|\sum|$ 有关,所以再枚举 $|\sum|$,这样我们需要在包含当前需要的字符集的字符集位置加上 $cnt ^ {|\sum|}$。这个可以使用 FMT 简单的实现。于是总时间复杂度 $O(|\sum| ^ 22 ^ {|\sum|} + n ^ 2|\sum|)$。
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 int main () { scanf ("%d%s" , &n, str + 1 ); for (int j = 1 ; j <= 17 ; ++ j) for (int i = pw[j][0 ] = 1 ; i <= n; ++ i) pw[j][i] = (LL) pw[j][i - 1 ] * j % Mod; int totcnt = 0 ; for (int i = 1 ; i <= n; ++ i) totcnt += str[i] == '?' ; for (int mid = 1 ; mid <= n; ++ mid) { int len = 0 , sta = 0 , cnt = 0 , excnt = totcnt; while (mid > len && mid + len <= n) { int i = mid - len, j = mid + len; if (str[i] != '?' && str[j] != '?' ) { if (str[i] != str[j]) break ; } else if (str[i] == '?' && str[j] == '?' ) cnt ++; else sta |= 1 << ((str[i] == '?' ? str[j] : str[i]) - 'a' ); excnt -= (str[i] == '?' ) + (str[j] == '?' ); if (i == j && str[i] == '?' ) excnt ++; for (int j = 1 ; j <= 17 ; ++ j) adj (ans[j][sta] += pw[j][cnt + excnt] - Mod); len ++; } } for (int mid = 1 ; mid <= n; ++ mid) { int len = 1 , sta = 0 , cnt = 0 , excnt = totcnt; while (mid >= len && mid + len <= n) { int i = mid - len + 1 , j = mid + len; if (str[i] != '?' && str[j] != '?' ) { if (str[i] != str[j]) break ; } else if (str[i] == '?' && str[j] == '?' ) cnt ++; else sta |= 1 << ((str[i] == '?' ? str[j] : str[i]) - 'a' ); excnt -= (str[i] == '?' ) + (str[j] == '?' ); for (int j = 1 ; j <= 17 ; ++ j) adj (ans[j][sta] += pw[j][cnt + excnt] - Mod); len ++; } } for (int j = 1 ; j <= 17 ; ++ j) FMT (ans[j]); std::cin >> Q; while (Q --) { scanf ("%s" , tmp + 1 ); int sta = 0 ; for (int j = 1 ; tmp[j]; ++ j) sta |= 1 << (tmp[j] - 'a' ); printf ("%d\n" , ans[popcount (sta)][sta]); } return 0 ; }
F 题意:给定长度为 $n$ 仅包含 $0\sim 9$ 的字符串相同,给定 $m$ 个交换,相邻两个出现在里面时可以交换。如果两个字符串可以互相交换得到,则称两个字符串相同。问本质不同的字符串有多少个。$n\leq 50000$。
首先我们定义代表元为本质相同的字符串中字典序最小的。这样,如果我们存在满足中间某些位是 $(d_1, d_2, d_3\dots, d_k)$ 并且 $(d_i, d_{i + 1})$ 中间可以交换,$d_1 > d_k$,那么这个字符串就不是最小的。记录前面可以互相交换的 $d_1,\dots, d_k$,如果存在一个 $d$ 满足 $d > nxt$ 并且 $nxt$ 中可以有转移 $(d, nxt)$,那么加入 $nxt$ 之后就不是最小的了。(注意前面的都可以放到 $d_k$ 这个位置来和 $nxt$ 交换,如果存在 $d > nxt$,可以将 $nxt$ 交换到 $d_k$ 的位置,$d$ 交换到 $nxt$ 的位置,就不是最小的
注意到我们只需要哪些元素出现没有,并不需要出现的次数及顺序。于是直接状压,将 $sta$ 记作前面 $d_1\dots, d_k$ 的并集。转移的时候观察存在 $(nxt, d)$ 且 $nxt < d$ 的 $nxt$ 无法转移,其余均可。转移到的就是 $sta$ 中能和 $nxt$ 交换的数和 $nxt$ 本身。判断大于的有没有记录一下大于 $nxt$ 的所有转移,压一下状态即可。
时间复杂度 $O(n|\sum|2 ^ {|\sum|})$,可以通过。
注意寻找代表元的做法统计本质不同个数 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int main () { std::cin >> n >> m; for (int i = 1 , u, v; i <= m; ++ i) { std::cin >> u >> v; A[u] |= 1 << v, B[u] |= 1 << v, A[v] |= 1 << u; } for (int i = 0 ; i < 10 ; ++ i) f[1 ][1 << i] = 1 ; for (int i = 2 ; i <= n; ++ i) { for (int s = 0 ; s < S; ++ s) f[i & 1 ][s] = 0 ; for (int s = 0 , cur; s < S; ++ s) { if (!(cur = f[(i - 1 ) & 1 ][s])) continue ; for (int nxt = 0 ; nxt < 10 ; ++ nxt) if (!(B[nxt] & s)) adj (f[i & 1 ][(A[nxt] & s) | (1 << nxt)] += cur - Mod); } } int res = 0 ; for (int i = 0 ; i < S; ++ i) adj (res += f[n & 1 ][i] - Mod); std::cout << res << std::endl; return 0 ; }