【10.4校内测试】【轮廓线DP】【中国剩余定理】【Trie树+博弈】
考场上几乎是一看就看出来轮廓线叻...可是调了两个小时打死也过不了手出样例!std发下来一对,特判对的啊,转移对的啊,$dp$数组竟然没有取max!!!
某位考生当场死亡。
结果下午又请了诸位dalao来看为什么剩下wa两个点!结果数组开小。
某位考生再次死亡。
#include<bits/stdc++.h> #define RG register using namespace std; int dp[2][(1<<16)+1], cnt[(1<<16)+1]; int R, C, a[129][17];////////不开够影响很大!! int count(int sta) { int num = 0; while(sta) { if(sta & 1) num ++; sta >>= 1; } return num; } void init() { scanf("\n"); for(int i = 1; i <= R; i ++) { char s; int cnt = 0; s = getchar(); while(s != '\n') { a[i][++a[i][0]] = s - 'A' + 1; s = getchar(); } } for(int i = 1; i < (1 << C); i ++) cnt[i] = count(i); } struct Node { int n1, n2; Node(int n1 = 0, int n2 = 0) : n1(n1), n2(n2) { } }; inline Node check(int sta, int pos, int line) { int s1 = sta >> (pos - 1), s2 = sta & ((1 << (pos - 1)) - 1); int num1 = cnt[s1], num2 = cnt[s2]; if(num1 > a[line-1][0] || num2 > a[line][0] || num2 + C - pos + 1 < a[line][0] || num1 + pos - 1 < a[line-1][0]) return Node(-1, -1); return Node(num1, num2); } int main() { freopen("group.in", "r", stdin); freopen("group.out", "w", stdout); scanf("%d%d", &R, &C); init(); int now = 0; for(RG int i = 1; i <= R; i ++) { for(RG int j = 1; j <= C; j ++) { now ^= 1; memset(dp[now], 0, sizeof(dp[now])); for(RG int s = 0; s < (1 << C); s ++) { int pre = s & (1 << (C - 1)); int las = s & 1; Node opt = check(s, j, i); if(opt.n1 == -1) continue; int q = opt.n1, p = opt.n2; int num1, num2; if(!pre) num1 = 0; else num1 = a[i-1][a[i-1][0]-q+1]; if(!las || j == 1) num2 = 0; else num2 = a[i][p]; if(p + 1 <= a[i][0]) { int ss = (s ^ pre) << 1 | 1; int t = dp[now ^ 1][s]; if(a[i][p+1] == num1 && pre) t += 2; if(a[i][p+1] == num2 && las) t += 2; dp[now][ss] = max(t, dp[now][ss]);////我暴毙 } int ss = (s ^ pre) << 1; dp[now][ss] = max(dp[now][ss], dp[now ^ 1][s]); } } } int ans = 0; for(int s = 0; s < (1 << C); s ++) { if(cnt[s] != a[R][0]) continue; ans = max(ans, dp[now][s]); } printf("%d", ans); return 0; }
我们可以发现在模数为质数时,可以直接用组合+求逆元计算出来,但是求逆元只能是在模数与要求逆元数互质时才行。
又因为题目明显暗示$MOD$由质数组成,所以直接套中国剩余定理即可。需要注意的是,对于每个分解出来的质因子都要重新求对应的逆元和阶乘。
#include<bits/stdc++.h> #define LL long long using namespace std; int n, m, T, MOD; LL fac[100005], va[100005], vm[100005], inv[100005]; LL isnot[100005], prime[100005], t; void div() { isnot[1] = 1; for(int i = 2; i <= 100000; i ++) { if(!isnot[i]) prime[++t] = i; for(int j = 1; j <= t; j ++) { int to = prime[j] * i; if(to > 100000) break; isnot[to] = 1; if(i % prime[j] == 0) break; } } } LL tot; void init() { div(); LL tmp = MOD; for(LL i = 2; i * i <= tmp && tmp != 1; i ++) if(tmp % i == 0) vm[++tot] = i, tmp /= i; if(tmp > 1) vm[++tot] = tmp; } LL mpow(LL a, LL b, LL mod) { LL ans = 1; for(; b; b >>= 1, a = a * a % mod) if(b & 1) ans = ans * a % mod; return ans; } LL rev(LL a, LL mod) { return mpow(a, mod - 2, mod); } LL C(LL q, LL p, LL mod) { if(p > q) return 0; return fac[q] * inv[p] % mod * inv[q-p] % mod; } LL Lucas(LL x, LL y, LL mod) { if(x < y) return 0; if(y == 0) return 1; return Lucas(x / mod, y / mod, mod) * C(x % mod, y % mod, mod) % mod; } LL Chinese_remainder_theorem() { LL ans = 0; for(LL i = 1; i <= tot; i ++) { LL mi = MOD / vm[i]; LL rei = rev(mi, vm[i]); ans = (ans + mi * rei % MOD * va[i] % MOD) % MOD; } return ans; } int main() { freopen("visit.in", "r", stdin); freopen("visit.out", "w", stdout); scanf("%d%d", &T, &MOD); scanf("%d%d", &n, &m); if(n < 0) n = -n; if(m < 0) m = -m; int c = (T - n - m) / 2; if(T < n + m || (n + m - T) % 2 == 1) { printf("0\n"); return 0; } init(); for(LL k = 1; k <= tot; k ++) { fac[0] = 1; for(long long i = 1;i <= T;i ++) fac[i] = 1ll * fac[i-1] * i % vm[k]; inv[0] = inv[1] = 1; for(long long i = 2;i <= T;i ++) inv[i] = 1ll * inv[vm[k] % i] * (vm[k] - vm[k] / i) % vm[k]; for(long long i = 2;i <= T;i ++) inv[i] = 1ll * inv[i] * inv[i - 1] % MOD; for(LL i = 0; i <= c; i ++) { LL j = c - i; va[k] = (va[k] + 1ll * Lucas(T, i, vm[k]) * Lucas(T-i, j, vm[k]) % vm[k] * 1ll * Lucas(T-i-j, i+n, vm[k]) % MOD) % MOD; } } LL ans = Chinese_remainder_theorem(); printf("%lld", ans); return 0; }
由字符串的前缀和想到建$Trie$树。我们发现,对于$Trie$树上某一节点,如果它的儿子有一个是可以选择必胜,那么当前节点就可以选择必败;如果它的儿子有一个是可以选择必败,那么当前节点就可以选择必胜;如果它的儿子全都可胜可败,那么它就没有选择权利;如果它的儿子有一个没有选择权利,那么它就可胜可败。在$Trie$树上直接深搜处理出每个节点的状态。
出来后如果根节点可胜可败,那么$Pure$就可以选择前面所有局都输,最后胜,因此她必胜;如果根节点必败,那么$Dirty$必胜;如果根节点必胜,那么要看局数$k$的奇偶性;如果根节点无法选择,也是$Dirty$必胜。
#include<bits/stdc++.h> using namespace std; int n, k; int son[100005][27], tail; char str[100005]; void add(char *s) { int nd = 0; int len = strlen(s); for(int i = 0; i < len; i ++) { int t = s[i] - 'a'; if(!son[nd][t]) son[nd][t] = ++ tail; nd = son[nd][t]; } } int dp[2700005]; int Dfs(int u) { int fl = -1, sum = 0, num = 0; for(int i = 0; i < 26; i ++) { if(son[u][i]) { sum ++; fl = Dfs(son[u][i]); if(fl == 1) dp[u] |= 2; if(fl == 2) dp[u] |= 1; if(fl == 0) dp[u] |= 3; if(fl == 3) num ++; } } if(num == sum) dp[u] = 0; if(fl == -1) dp[u] = 1; return dp[u]; } int main() { freopen("strGame.in", "r", stdin); freopen("strGame.out", "w", stdout); int T; scanf("%d", &T); while(T --) { memset(son, 0, sizeof(son)); memset(dp, 0, sizeof(dp)); tail = 0; scanf("%d%d", &n, &k); for(int i = 1; i <= n; i ++) { scanf("%s", str); add(str); } Dfs(0); if(dp[0] == 3) printf("Pure\n"); else if(dp[0] == 1) { printf("Dirty\n"); } else if(dp[0] == 2) { if(k % 2) printf("Pure\n"); else printf("Dirty\n"); } else printf("Dirty\n"); } return 0; }