【比赛链接】
【题解链接】
【A】 Triangle
【思路要点】
- 注意到对于任意一个格点三角形,我们都可以找到一个点,将其平移至原点处后,另外两个点在同一象限内,或在该象限相邻的坐标轴上。
- 因此,可以不失一般性地假定一个点在 ( 0 , 0 ) (0,0) (0,0) 处。
- 令另外两点的坐标为 ( x , y ) , ( a , b ) (x,y),(a,b) (x,y),(a,b) ,则要求 x b − y a = S xb-ya=S xb−ya=S 。
- 简单构造一组解即可。
- 时间复杂度 O ( 1 ) O(1) O(1) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int main() { ll s; read(s); int lim = 1e9; int x = s / lim; if (s % lim) x++; int b = s / x; if (s % x) b++; int y = 1ll * x * b - s; printf("%d %d %d %d %d %d\n", 0, 0, x, y, 1, b); return 0; }
【B】 Do Not Duplicate
【思路要点】
- 记 A n s i Ans_i Ansi 表示对空串加入 a i , a i + 1 , … , a N a_i,a_{i+1},\dots,a_N ai,ai+1,…,aN 后得到的数组, A n s 0 Ans_0 Ans0 表示空串。
- 可以发现, K K K 取任意值时,得到的结果总是 A n s i Ans_i Ansi 中的一个。
- 这是因为对于 A n s 0 Ans_0 Ans0 加入 a 1 , a 2 , … , a N a_1,a_2,\dots,a_N a1,a2,…,aN 后将得到 A n s 1 Ans_1 Ans1 ;对于 A n s i Ans_{i} Ansi ,在加入 A n s i , 1 Ans_{i,1} Ansi,1 第一次出现的位置 j j j 时,串将变为空串,于是会得到 A n s j + 1 Ans_{j+1} Ansj+1 。
- 在这样的关系上倍增或是找周期即可。
- 时间复杂度 O ( N L o g K ) O(NLogK) O(NLogK) 或 O ( N ) O(N) O(N) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; const int MAXLOG = 60; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, a[MAXN], f[MAXN], nxt[MAXN]; int dest[MAXN][MAXLOG]; ll k; int main() { read(n), read(k); for (int i = 1; i <= n; i++) read(a[i]); for (int i = n; i >= 1; i--) f[a[i]] = i; for (int i = n; i >= 1; i--) { nxt[i] = f[a[i]]; f[a[i]] = i; } dest[0][0] = 1; for (int i = n; i >= 1; i--) { if (nxt[i] <= i) { if (nxt[i] == n) dest[i][0] = 0; else dest[i][0] = nxt[i] + 1; } else { if (nxt[i] == n) dest[i][0] = 1; else dest[i][0] = dest[nxt[i] + 1][0]; } } for (int p = 1; p < MAXLOG; p++) for (int i = 0; i <= n; i++) dest[i][p] = dest[dest[i][p - 1]][p - 1]; int ans = 0; for (int i = 0; i < MAXLOG; i++) if (k & (1ll << i)) ans = dest[ans][i]; memset(f, 0, sizeof(f)); static int b[MAXN]; int tot = 0; if (ans == 0) return 0; for (int i = ans; i <= n; i++) if (f[a[i]] == 0) { b[++tot] = a[i]; f[a[i]] = tot; } else { int pos = f[a[i]]; while (tot >= pos) f[b[tot--]] = 0; } for (int i = 1; i <= tot; i++) printf("%d ", b[i]); return 0; }
【C】 GP 2
【思路要点】
- 一个局面合法有一些显然的必要条件:
( 1 ) (1) (1) 、所有数的和为 3 M 3M 3M
( 2 ) (2) (2) 、所有数的最大值不超过 2 M 2M 2M
( 3 ) (3) (3) 、所有数中奇数的个数不超过 M M M- 考虑对最大值和除去该数的剩余奇数中的最大值进行分类讨论,可以用归纳法证明,这些条件也是充分的。
- 考虑如何计数,如果没有限制 ( 2 ) (2) (2) ,则可以枚举奇数的个数 i i i ,贡献即为 ( N i ) × ( 3 M − i 2 + N − 1 N − 1 ) \binom{N}{i}\times\binom{\frac{3M-i}{2}+N-1}{N-1} (iN)×(N−123M−i+N−1) 。
- 那么对限制 ( 2 ) (2) (2) 进行容斥即可,即计算满足限制 ( 1 ) , ( 3 ) (1),(3) (1),(3) ,且最大值超过 2 M 2M 2M 的方案数,同样可以枚举奇数的个数 i i i 进行计算。
- 时间复杂度 O ( N + M ) O(N+M) O(N+M) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 3e6 + 5; const int P = 998244353; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int fac[MAXN], inv[MAXN]; int power(int x, int y) { if (y == 0) return 1; int tmp = power(x, y / 2); if (y % 2 == 0) return 1ll * tmp * tmp % P; else return 1ll * tmp * tmp % P * x % P; } int binom(int x, int y) { if (y > x) return 0; else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P; } void init(int n) { fac[0] = 1; for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % P; inv[n] = power(fac[n], P - 2); for (int i = n - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1ll) % P; } void update(int &x, int y) { x += y; if (x >= P) x -= P; } int main() { int n, m, ans = 0; read(n), read(m); init(n + 3 * m); int s = 3 * m; for (int i = 0; i <= m && i <= n; i++) { if ((s - i) & 1) continue; int tmp = (s - i) / 2; update(ans, 1ll * binom(n, i) * binom(tmp + n - 1, n - 1) % P); } s -= 2 * m; for (int i = 0; i <= m && i <= n; i++) { if ((s - i) & 1) continue; int tmp = (s - i) / 2; if (i != 0) update(ans, P - 1ll * n * binom(n - 1, i - 1) % P * binom(tmp + n - 1, n - 1) % P); if (tmp != 0) update(ans, P - 1ll * n * binom(n - 1, i) % P * binom(tmp + n - 2, n - 1) % P); } writeln(ans); return 0; }
【D】 Negative Cycle
【思路要点】
- 考虑 0 0 0 号节点到每个节点 i i i 的最短路 d i s i dis_i disi ,由于本来存在的边, d i s i dis_i disi 是递减的,且相邻的位置相差至多为 1 1 1 。
- 考虑一个确定的最短路序列 d i s i dis_i disi ,我们需要删掉一些边以确保其不能被更新,所花费的代价即为 d i s i dis_i disi 对应的代价。
- 计算代价的过程是直观的,考虑相邻的两段 d i s i dis_i disi 相同的区间 [ a , b ] , [ b + 1 , c ] [a,b],[b+1,c] [a,b],[b+1,c] ,其中 [ b + 1 , c ] [b+1,c] [b+1,c] 中连出的边需要删掉连向 [ b + 2 , c ] [b+2,c] [b+2,c] 的向前的边,以及连向 [ 0 , a − 1 ] [0,a-1] [0,a−1] 的向后的边。
- 不难发现在状态中计入 a , b a,b a,b ,转移时枚举 c c c ,利用部分和优化即可得到一个 O ( N 3 ) O(N^3) O(N3) 的动态规划解法。
- 时间复杂度 O ( N 3 ) O(N^3) O(N3) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 505; const long long INF = 1e18; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, a[MAXN][MAXN]; ll s[MAXN][MAXN], cost[MAXN][MAXN], dp[MAXN][MAXN]; int main() { read(n); for (int i = 1; i <= n; i++) { for (int j = 1; j < i; j++) read(a[i][j]); for (int j = i + 1; j <= n; j++) read(a[i][j]); } for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) s[i][j] = s[i][j - 1] + a[i][j]; for (int i = 0; i <= n; i++) for (int j = i + 1; j <= n; j++) dp[i][j] = INF; for (int i = 1; i <= n; i++) for (int j = i; j <= n; j++) { ll res = 0; for (int k = i; k <= j; k++) res += s[k][j] - s[k][k]; cost[i][j] = res; } for (int i = 1; i <= n; i++) dp[0][i] = cost[1][i]; for (int i = 0; i <= n - 1; i++) for (int j = i + 1; j <= n; j++) { ll tmp = dp[i][j]; for (int k = j + 1; k <= n; k++) { tmp += s[k][i]; chkmin(dp[j][k], tmp + cost[j + 1][k]); } } ll ans = INF; for (int i = 0; i <= n - 1; i++) chkmin(ans, dp[i][n]); writeln(ans); return 0; }
【E】 ABC String
【思路要点】
- 不难发现应该将连续的字符当成一个看待,记此时 A A A 的个数为 a a a , B B B 的个数为 b b b , C C C 的个数为 c c c ,不失一般性地设 a ≤ b ≤ c a\leq b\leq c a≤b≤c。
- 考虑选定一个 A A A 的集合,删除其余的 A A A ,并将相同的字符重新并起来,由于删除一个 A A A 至多使得 b , c b,c b,c 减少 1 1 1 ,因此依然有 a ≤ b , a ≤ c a\leq b,a\leq c a≤b,a≤c 。
- 仍然设 a ≤ b ≤ c a\leq b\leq c a≤b≤c ,考虑删除一些 B , C B,C B,C ,使得字符串满足条件。
- 若 b = c b=c b=c ,我们可以不断地删除不会使得字符串中出现 A A AA AA 的 B C BC BC 或 C B CB CB 。
- 否则,即 b < c b<c b<c ,记总共 a + 1 a+1 a+1 段字符中包含 B B B 的段数为 x x x ,中间的 a − 1 a-1 a−1 段字符中仅由一个 C C C 组成的段数为 y y y 。
- 若 x < y x<y x<y ,显然是无法通过删除一些 B , C B,C B,C 使得 b = c b=c b=c 的,否则,就说明包含 B B B 的段数不少于仅由一个 C C C 组成的段数,可以不断地删除不会使得字符串中出现连续相同字符的 C C C ,使得 b = c b=c b=c 。
- 因此,我们需要在原串中选出尽量多的 A A A ,满足得到的字符串中 x ≥ y x\geq y x≥y 。
- 考虑先选取所有的 A A A ,若 x ≥ y x\geq y x≥y ,则找到的一定是最优解,否则记 d = y − x d=y-x d=y−x ,删去 d d d 个与单独的 C C C 相邻的 A A A 即可。
- 时间复杂度 O ( ∣ S ∣ ) O(|S|) O(∣S∣) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1e6 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } char s[MAXN]; int n; vector <pair <int, int>> mod; void modify(char *s, int n, int x, int y) { for (int i = 1; i <= n; i++) if (s[i] == x + 'A') s[i] = y + 'A'; else if (s[i] == y + 'A') s[i] = x + 'A'; } void init() { scanf("%s", s + 1); int m = strlen(s + 1); int cnt[3] = {0, 0, 0}; for (int i = 1; i <= m; i++) if (s[n] != s[i]) { s[++n] = s[i]; cnt[s[i] - 'A']++; } s[n + 1] = 0; if (cnt[0] > cnt[1]) swap(cnt[0], cnt[1]), modify(s, n, 0, 1), mod.emplace_back(0, 1); if (cnt[1] > cnt[2]) swap(cnt[1], cnt[2]), modify(s, n, 1, 2), mod.emplace_back(1, 2); if (cnt[0] > cnt[1]) swap(cnt[0], cnt[1]), modify(s, n, 0, 1), mod.emplace_back(0, 1); if (cnt[0] == 0) puts(""), exit(0); } void sela() { int b = 0, c = 0; static int pos[MAXN]; int cnt = 0; for (int i = 1; i <= n; i++) if (s[i] == 'A') pos[++cnt] = i; for (int i = 1; i <= cnt; i++) { bool occur = false; for (int j = pos[i - 1] + 1; j <= pos[i] - 1; j++) occur |= s[j] == 'B'; b += occur; } bool occur = false; for (int j = pos[cnt] + 1; j <= n; j++) occur |= s[j] == 'B'; b += occur; for (int i = 1; i <= cnt - 1; i++) c += pos[i] == pos[i + 1] - 2 && s[pos[i] + 1] == 'C'; if (b >= c) return; int lft = c - b, tmp = n; n = 0; for (int i = 1; i <= tmp; i++) if (s[i] == 'A') { if (lft != 0 && n >= 2 && s[n] == 'C' && s[n - 1] == 'A') lft--; else s[++n] = s[i]; } else if (s[n] != s[i]) s[++n] = s[i]; assert(lft == 0); } void delc() { int delta = 0; for (int i = 1; i <= n; i++) if (s[i] == 'C') delta++; else if (s[i] == 'B') delta--; assert(delta >= 0); int tmp = n; n = 0; s[tmp + 1] = 0; for (int i = 1; i <= tmp; i++) if (s[i] == 'C') { if (delta != 0 && ((n >= 1 && s[n] != s[i + 1]) || n == 0)) delta--; else s[++n] = s[i]; } else s[++n] = s[i]; assert(delta == 0); } void delb() { int a = 0, b = 0, c = 0; for (int i = 1; i <= n; i++) if (s[i] == 'C') c++; else if (s[i] == 'B') b++; else a++; assert(b == c && b >= a); int tmp = n; n = 0; s[tmp + 1] = -1; for (int i = 1; i <= tmp; i++) if (s[i] == 'A') s[++n] = s[i]; else if (b > a && n >= 1 && s[n] != 'A' && s[i] != s[n] && s[i + 1] != s[n - 1]) n--, b--, c--; else s[++n] = s[i]; s[n + 1] = 0; } int main() { init(); sela(); delc(); delb(); while (!mod.empty()) { modify(s, n, mod.back().first, mod.back().second); mod.pop_back(); } printf("%s\n", s + 1); return 0; }
【F】 Square Constraints
【思路要点】
- 考虑没有下界的情况,显然每一个点可以匹配的位置是一个前缀,记这些前缀的长度分别为 a 0 , a 1 , a 2 , … ( a 0 ≤ a 1 ≤ a 2 ≤ …   ) a_0,a_1,a_2,\dots\ (a_0\leq a_1\leq a_2\leq\dots) a0,a1,a2,… (a0≤a1≤a2≤…) ,那么方案数即为 ∏ i ( a i − i ) \prod_{i}(a_i-i) ∏i(ai−i) 。
- 记 f ( i ) f(i) f(i) 表示平方和 ≤ ( 2 N ) 2 \leq (2N)^2 ≤(2N)2 可以匹配的前缀长度, g ( i ) g(i) g(i) 表示平方和 < N 2 <N^2 <N2 可以匹配的前缀长度,若 i ≥ N i\geq N i≥N 则为 0 0 0 。
- 考虑对下界进行容斥,一个直观地想法是选择 i i i 个位置取 g ( i ) g(i) g(i) ,其余位置取 f ( i ) f(i) f(i) ,排序后计算匹配数,乘上 ( − 1 ) i (-1)^i (−1)i 计入答案。
- 一点关键的观察是如果将不是 0 0 0 的 f ( i ) , g ( i ) f(i),g(i) f(i),g(i) ,共 3 N 3N 3N 个数排序,所有 ≤ N − 1 \leq N-1 ≤N−1 的 i i i 对应的 f ( i ) f(i) f(i) 会排在最后 N N N 个位置,这是因为这些数 ≥ N \geq N ≥N ,而其余数 ≤ N \leq N ≤N 。
- 据此,枚举上面选取的位置数 i i i ,我们可以直观地用一个 O ( N 2 ) O(N^2) O(N2) 的动态规划加速上面的枚举,即记 d p j , k dp_{j,k} dpj,k 表示考虑了排在前面的 j j j 个位置,选择了 k k k 个 g ( i ) g(i) g(i) 的方案数,转移时将对应的 f ( i ) , g ( i ) f(i),g(i) f(i),g(i) 一同考虑即可。
- 时间复杂度 O ( N 3 ) O(N^3) O(N3) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 255; const int MAXM = 505; const int INF = 1e9; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, m, Q, P; int dp[MAXM][MAXN]; pair <int, int> a[MAXM]; void update(int &x, int y) { x += y; if (x >= P) x -= P; } int main() { read(n), read(Q), P = INF / Q * Q; int p = 2 * n - 1, q = 2 * n - 1; for (int i = 0; i <= 2 * n - 1; i++) { while (i * i + p * p > (2 * n) * (2 * n) && p >= 0) p--; while (i * i + q * q >= n * n && q >= 0) q--; if (q == -1) a[++m] = make_pair(p + 1, 0); else a[++m] = make_pair(q + 1, p + 1); } sort(a + 1, a + m + 1); int ans = 0; for (int k = 0; k <= n; k++) { memset(dp, 0, sizeof(dp)), dp[0][0] = 1; for (int i = 1, f = 0, g = 0; i <= m; f += a[i].second != 0, g += a[i].second == 0, i++) for (int j = 0; j <= k; j++) { int tmp = dp[i - 1][j]; if (a[i].second == 0) update(dp[i][j], 1ll * tmp * (a[i].first - j - g) % P); else { if (j != k) update(dp[i][j + 1], 1ll * tmp * (a[i].first - j - g) % P); update(dp[i][j], 1ll * tmp * (a[i].second - k - n - (f - j)) % P); } } int now = dp[m][k]; if (k & 1) update(ans, P - now); else update(ans, now); } writeln(ans % Q); return 0; }