博弈论 + 计数,AC 自动机 + dp,格路计数
# A. 取石子
看了几个小时才想到怎么博弈。
我们把 的余数分类讨论,设余数为 。
假设 .
下面来分析一下:
情况 1 对胜负没用,忽略掉,不过最后计数的时候要乘上 。
出现情况 2,那么 必胜,因为这步可以留到最后走。
出现情况 3,那么相当于交换先后手。
出现情况 4:
出现次数大于 1:那么 必胜,可以手模一下。
只出现 1 次, 先手则先手胜, 先手则交换先手顺序。
如果 的话把 3,4 合并起来就行了(好像)
然后把四种情况记到桶里。
计算或者 都行。
#include <bits/stdc++.h> | |
#define pb push_back | |
// #define int long long | |
using namespace std;  | |
const int N = 1e5 + 10;  | |
const int mod = 1e9 + 7;  | |
int n, a, b;  | |
int s[N], c[N], tot, tmp;  | |
inline int add(int x) {return x >= mod ? x - mod : x;}  | |
inline int sub(int x) {return x < 0 ? x + mod : x;}  | |
inline int mul(int x, int y) {return 1ll * x * y % mod;}  | |
inline int qpow(int a, int b, int v){  | |
if(b < 0) return v;  | |
int res = 1;  | |
for(; b; b >>= 1, a = mul(a, a))  | |
if(b & 1) res = mul(res, a);  | |
return res;  | |
} | |
int t[4], win[4];  | |
// 0: alice 胜  1: Bob 胜  2: 先手  3: 后手 | |
int d[N], k, flag;  | |
signed main(){  | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin);  | |
freopen("test.out", "w", stdout);  | |
#endif | |
ios :: sync_with_stdio(false);  | |
cin >> n >> a >> b;  | |
if(a > b) swap(a, b), flag = 1;  | |
for(int i = 1; i <= n; ++i) cin >> s[i];  | |
for(int i = 1; i <= n; ++i){  | |
int r = s[i] % (a + b);  | |
if(r < a) t[0]++;  | |
if(a <= r && r < b) t[1]++;  | |
if(b < 2 * a){  | |
if(b <= r && r < 2 * a) t[2]++;  | |
if(2 * a <= r && r <= a + b - 1) t[3]++;  | |
}else{  | |
if(b <= r && r <= a + b - 1) t[3]++;  | |
        } | |
    } | |
win[2] = add(qpow(2, t[2] - 1, 0) + mul(t[3], qpow(2, t[2] - 1, 1)));  | |
win[2] = mul(win[2], qpow(2, t[0], 1));  | |
win[3] = mul(qpow(2, t[2] - 1, 1), qpow(2, t[0], 1));  | |
win[0] = sub(qpow(2, n, 0) - add(win[2] + win[3]));  | |
if(flag) swap(win[0], win[1]);  | |
for(int i = 0; i < 4; ++i) cout << win[i] << " ";  | |
cout << '\n';  | |
return 0;  | |
} | 
# B. 字符串
妙题。
我们对 的正反串分别建两个 AC 自动机,设为 。
记一个 ,设 表示填了 个字符,包含了状态为 的串,当前在 的 点, 的 点时的方案数。
转移时枚举下一个填 ,对称的位置是固定的,让 跳到相应的 点,状态或上 包含的子串。
转移方程:
这时还有一个小问题,就是有一些串越过中间的位置。
我们预处理出一个数组 表示现在在 中的 点, 中 点时,两边拼接能够包含的 个模式串的 01 状态。
统计答案时枚举状态,再枚举所有符合这个状态的 ,把 跟 或起来看一下是否合法即可。
#include <bits/stdc++.h> | |
#define pb push_back | |
#define fi first | |
#define se second | |
#define int long long | |
using namespace std;  | |
const int N = 610;  | |
const int mod = 998244353;  | |
int n, m;  | |
char s[10][N];  | |
int b[N], len[10];  | |
inline void add(int &x, int y) {x = x + y >= mod ? x + y - mod : x + y;}  | |
struct ACAN{  | |
int ch[N][2], tot, num[N], fail[N];  | |
vector <int> G[N];  | |
inline void insert(char *s, int id){  | |
int len = strlen(s + 1), u = 0;  | |
for(int i = 1; i <= len; ++i){  | |
int c = s[i] - '0';  | |
if(!ch[u][c]) ch[u][c] = ++tot;  | |
u = ch[u][c];  | |
        } | |
num[u] |= (1 << id);  | |
    } | |
inline void build(){  | |
queue <int> q;  | |
for(int i = 0; i < 2; ++i)  | |
if(ch[0][i]) q.push(ch[0][i]);  | |
while(!q.empty()){  | |
int x = q.front(); q.pop();  | |
for(int i = 0; i < 2; ++i){  | |
if(ch[x][i]) fail[ch[x][i]] = ch[fail[x]][i], q.push(ch[x][i]);  | |
else ch[x][i] = ch[fail[x]][i];  | |
            } | |
        } | |
for(int i = 1; i <= tot; ++i) G[fail[i]].pb(i), num[i] |= num[fail[i]];  | |
    } | |
}A, B;  | |
int g[N][N];  | |
inline int jump1(int p, int l, int r){  | |
int x = 0;  | |
for(int i = l; i <= r; ++i) x = A.ch[x][s[p][i] - '0'];  | |
return x;  | |
} | |
inline int jump2(int p, int l, int r){  | |
int x = 0;  | |
for(int i = r; i >= l; --i) x = B.ch[x][s[p][i] - '0'];  | |
return x;  | |
} | |
inline void dfs2(int x, int y){  | |
g[x][y] |= g[x][B.fail[y]] | g[A.fail[x]][y];  | |
for(auto v : B.G[y]) dfs2(x, v);  | |
} | |
inline void dfs1(int x, int y){  | |
dfs2(x, y);  | |
for(auto v : A.G[x]) dfs1(v, y);  | |
} | |
typedef pair<int, int> P;  | |
int f[2][64][N][N];  | |
vector <P> vec[2][64];  | |
signed main(){  | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin);  | |
freopen("test.out", "w", stdout);  | |
#endif | |
ios :: sync_with_stdio(false);  | |
cin >> m >> n;  | |
for(int i = 0; i < m; ++i){  | |
cin >> (s[i] + 1), len[i] = strlen(s[i] + 1);  | |
A.insert(s[i], i), reverse(s[i] + 1, s[i] + 1 + len[i]);  | |
B.insert(s[i], i), reverse(s[i] + 1, s[i] + 1 + len[i]);  | |
    } | |
A.build(), B.build();  | |
for(int i = 0; i < m; ++i)  | |
for(int j = 1; j < len[i]; ++j)  | |
g[jump1(i, 1, j)][jump2(i, j + 1, len[i])] |= (1 << i);  | |
dfs1(0, 0);  | |
f[0][0][0][0] = 1;  | |
vec[0][0].pb(P(0, 0));  | |
for(int i = 0, l = 0; i < n; ++i, l ^= 1)  | |
for(int s = 0; s < (1 << m); ++s){  | |
for(auto p : vec[l][s]){  | |
for(int c = 0; c < 2; ++c){  | |
P q = P(A.ch[p.fi][c], B.ch[p.se][c ^ 1]);  | |
int t = s | A.num[q.fi] | B.num[q.se];  | |
if(!f[l ^ 1][t][q.fi][q.se]) vec[l ^ 1][t].pb(q);  | |
add(f[l ^ 1][t][q.fi][q.se], f[l][s][p.fi][p.se]);  | |
                } | |
f[l][s][p.fi][p.se] = 0;  | |
            } | |
vec[l][s].clear();  | |
        } | |
int ans = 0;  | |
for(int s = 0; s < (1 << m); ++s)  | |
for(auto p : vec[n & 1][s])  | |
if((s | g[p.fi][p.se]) == (1 << m) - 1) add(ans, f[n & 1][s][p.fi][p.se]);  | |
cout << ans << '\n';  | |
return 0;  | |
} | 
# C. 树
挺复杂的格路计数。
经过大量模拟不难发现不包含 连树相当于根到每个叶子上左儿子个数小于 。
考虑按先序遍历建出这棵树,每次有两种操作:
当前点加一个左孩子;
退到上一个只有左孩子的点,加一个右孩子。
转化成括号匹配,相当于一个长度为 的序列,保证左括号 - 右括号数量小于 。
当然还要保证括号序列合法,即前缀和大于等于 0。
如果只有前缀和大于等于 0 的条件,就是经典的卡特兰数的容斥:
总路径 - 不合法路径。大概就是把第一次碰到 -1 后面的路径全部翻折,最后到达 (不会的自行百度)
不合法方案数就是从 走到 的路径数。
然后现在有两个限制条件,我们的方案数就是:
( 表示经过 的方案数)
但是明显有重复的,所以就再套个容斥:
然后就没了。
#include <bits/stdc++.h> | |
#define pb push_back | |
using namespace std;  | |
const int N = 2e7 + 10;  | |
const int mod = 998244353;  | |
int n, m;  | |
int fac[N], ifac[N];  | |
inline int add(int x) {return x >= mod ? x - mod : x;}  | |
inline int sub(int x) {return x < 0 ? x + mod : x;}  | |
inline int mul(int x, int y) {return 1ll * x * y % mod;}  | |
inline int qpow(int a, int b){  | |
int res = 1;  | |
for(; b; b >>= 1, a = mul(a, a))  | |
if(b & 1) res = mul(res, a);  | |
return res;  | |
} | |
inline void init(int n){  | |
fac[0] = 1;  | |
for(int i = 1; i <= n; ++i) fac[i] = mul(fac[i - 1], i);  | |
ifac[n] = qpow(fac[n], mod - 2);  | |
for(int i = n - 1; i >= 0; --i) ifac[i] = mul(ifac[i + 1], i + 1);  | |
} | |
inline int C(int n, int m){  | |
return mul(fac[n], mul(ifac[m], ifac[n - m]));  | |
} | |
inline int f(int x){  | |
if(x > n || x < -n) return 0;  | |
return C(n, (n + x) / 2);  | |
} | |
signed main(){  | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin);  | |
freopen("test.out", "w", stdout);  | |
#endif | |
ios :: sync_with_stdio(false);  | |
cin >> n >> m, init(2e7);  | |
n = 2 * n - 2;  | |
int ans = f(0);  | |
for(int k = 2; (k - 2) * m <= n; k += 2){  | |
ans = sub(ans - add(f(k * m - 2) + f(-(k - 2) * m - 2)));  | |
ans = add(ans + add(f(k * m) + f(-k * m)));  | |
    } | |
cout << ans << '\n';  | |
return 0;  | |
} |