【比赛链接】
【题解链接】
【C】 Stones
【思路要点】
- 枚举最后的石子分布,用前缀和优化计算代价。
- 时间复杂度 O ( N ) O(N) O(N) 。
【代码】
#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 n; char s[MAXN]; int a[MAXN], b[MAXN]; int main() { read(n), scanf("%s", s + 1); for (int i = 1; i <= n; i++) a[i] = a[i - 1] + (s[i] == '#'); for (int i = n; i >= 1; i--) b[i] = b[i + 1] + (s[i] == '.'); int ans = n; for (int i = 0; i <= n; i++) chkmin(ans, a[i] + b[i + 1]); writeln(ans); return 0; }
【D】 Three Colors
【思路要点】
- 补集转化,考虑从所有方案中减去 R ≥ G + B R\geq G+B R≥G+B 或 G ≥ R + B G\geq R+B G≥R+B 或 B ≥ G + R B\geq G+R B≥G+R 的方案。
- 这样的计算方法会将 B = 0 , R = G B=0,R=G B=0,R=G 或 R = 0 , B = G R=0,B=G R=0,B=G 或 G = 0 , R = B G=0,R=B G=0,R=B 的方案减去两次,再加回来即可。
- 暴力 d p dp dp ,时间复杂度 O ( N × ∑ a i ) O(N\times\sum a_i) O(N×∑ai) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 305; 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 n, sum, dp[MAXN][MAXN * MAXN][2]; void update(int &x, int y) { x += y; if (x >= P) x -= P; } 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 main() { read(n), sum = 0; dp[0][0][0] = dp[0][0][1] = 1; for (int i = 1; i <= n; i++) { int x; read(x); sum += x; for (int j = 0; j <= sum; j++) { dp[i][j][0] = dp[i - 1][j][0]; dp[i][j][1] = dp[i - 1][j][1] * 2 % P; if (j >= x) { update(dp[i][j][0], dp[i - 1][j - x][0]); update(dp[i][j][1], dp[i - 1][j - x][1]); } } } int ans = power(3, n); for (int i = (sum + 1) / 2; i <= sum; i++) update(ans, P - 3ll * dp[n][i][1] % P); if (sum % 2 == 0) { //cerr << dp[n][sum / 2][0] << endl; update(ans, 3ll * dp[n][sum / 2][0] % P); } writeln(ans); return 0; }
【E】 Polynomial Divisors
【思路要点】
- f ( i ) ≡ 0 ( m o d p ) f(i)\equiv0\ (mod\ p) f(i)≡0 (mod p) 等价于 f ( i ) f(i) f(i) 在模 p p p 意义下存在因式 ( x − i ) (x-i) (x−i) 。
- 因此 f ( i ) f(i) f(i) 在模 p p p 意义下始终为零等价于 f ( i ) f(i) f(i) 在模 p p p 意义下存在因式 ∏ i = 0 p − 1 ( x − i ) ≡ x p − x ( m o d p ) \prod_{i=0}^{p-1}(x-i)\equiv x^p-x\ (mod\ p) ∏i=0p−1(x−i)≡xp−x (mod p) 。
- 那么,满足条件的 p p p 或是所有 a i a_i ai 的公因数,或在 N N N 以内。
- 所有 a i a_i ai 的公因数一定可以作为答案,在 N N N 以内的 p p p 可以 O ( N ) O(N) O(N) 判断。
- 时间复杂度 O ( N L o g V + V + N 2 L o g N ) O(NLogV+\sqrt{V}+\frac{N^2}{LogN}) O(NLogV+V+LogNN2) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1e4 + 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 tot, prime[MAXN], f[MAXN]; void sieve(int n) { for (int i = 2; i <= n; i++) { if (f[i] == 0) prime[++tot] = f[i] = i; for (int j = 1; j <= tot && prime[j] <= f[i]; j++) { int tmp = prime[j] * i; if (tmp > n) break; f[tmp] = prime[j]; } } } set <int> ans; int n, gcd, a[MAXN]; void factor(int x) { for (int i = 2; i * i <= x; i++) while (x % i == 0) { x /= i; ans.insert(i); } if (x != 1) ans.insert(x); } void update(int &x, int y, int p) { x = (x + y) % p; } void test(int p) { static int sum[MAXN]; for (int i = 0; i <= p - 2; i++) sum[i] = 0; for (int i = 0; i <= n; i++) update(sum[i % (p - 1)], a[i], p); for (int i = 0; i <= p - 2; i++) if (sum[i] != 0) return; ans.insert(p); } int main() { read(n), sieve(n); for (int i = n; i >= 0; i--) { read(a[i]); if (a[i] != 0) { int tmp = a[i]; if (tmp < 0) tmp = -tmp; if (gcd == 0) gcd = tmp; else gcd = __gcd(gcd, tmp); } } factor(gcd); for (int i = 1; i <= tot; i++) if (a[0] % prime[i] == 0) test(prime[i]); for (auto x : ans) writeln(x); return 0; }
【F】 Banned X
【思路要点】
- 首先枚举 0 0 0 的个数 i i i ,计算 f ( N − i , X ) f(N-i,X) f(N−i,X) 表示由 N − i N-i N−i 个 1 , 2 1,2 1,2 构成的,不存在和为 X X X 的子段的序列的个数,将 ( N i ) × f ( N − i , X ) \binom{N}{i}\times f(N-i,X) (iN)×f(N−i,X) 计入答案。
- 考虑由 1 , 2 1,2 1,2 构成的序列不存在和为 X X X 的子段的条件,需要满足以下其一:
( 1 ) (1) (1) 、序列和 S S S 不足 X X X 。
( 2 ) (2) (2) 、 S − X S-X S−X 是奇数,且序列仅由 2 2 2 组成。
( 3 ) (3) (3) 、 S − X S-X S−X 是奇数,且任何一个 1 1 1 及其左侧所有数的和不足 X X X ,任何一个 1 1 1 及其右侧所有数的和不足 X X X 。- 枚举序列和 S S S ,对于条件 ( 1 ) , ( 2 ) (1),(2) (1),(2) ,可以用组合数简单计算。
- 对于条件 ( 3 ) (3) (3) ,首先 S − X S-X S−X 是奇数,其次序列右侧和左侧都应有至少 S − X + 1 2 \frac{S-X+1}{2} 2S−X+1 个 2 2 2 ,中间可任意摆放,用组合数计算方案数即可。
- 时间复杂度 O ( N 2 ) O(N^2) O(N2) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 3e3 + 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 binom[MAXN][MAXN]; void update(int &x, int y) { x += y; if (x >= P) x -= P; } int getans(int n, int x) { int ans = 0; if (2 * n < x || x % 2 == 1) ans = 1; for (int i = n; i <= 2 * n - 1; i++) { if (i < x) update(ans, binom[n][i - n]); else { if ((i - x) % 2 == 0) continue; int cnt = (i - x + 1) / 2; if (2 * cnt >= n) continue; int rp = n - 2 * cnt, rs = i - 4 * cnt; if (rs >= rp && rs <= 2 * rp) update(ans, binom[rp][rs - rp]); } } return ans; } int main() { int n, x; read(n), read(x); for (int i = 0; i <= n; i++) { binom[i][0] = 1; for (int j = 1; j <= i; j++) binom[i][j] = (binom[i - 1][j - 1] + binom[i - 1][j]) % P; } int ans = 0; for (int i = 0; i <= n; i++) update(ans, 1ll * binom[n][i] * getans(n - i, x) % P); writeln(ans); return 0; }