博弈论 + 计数,AC自动机 + dp,格路计数
# A. 取石子
看了几个小时才想到怎么博弈。
我们把 的余数分类讨论,设余数为 。
假设 .
下面来分析一下:
-
情况 1 对胜负没用,忽略掉,不过最后计数的时候要乘上 。
-
出现情况 2,那么 必胜,因为这步可以留到最后走。
-
出现情况 3,那么相当于交换先后手。
-
出现情况 4:
-
出现次数大于 1:那么 必胜,可以手模一下。
-
只出现 1 次, 先手则先手胜, 先手则交换先手顺序。
-
如果 的话把 3,4合并起来就行了(好像)
然后把四种情况记到桶里。
计算或者 都行。
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
47
48
49
50
51
52
53
54
55
56
57
// #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(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
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 状态。
统计答案时枚举状态,再枚举所有符合这个状态的 ,把 跟 或起来看一下是否合法即可。
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
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 后面的路径全部翻折,最后到达 (不会的自行百度)
不合法方案数就是从 走到 的路径数。
然后现在有两个限制条件,我们的方案数就是:
( 表示经过 的方案数)
但是明显有重复的,所以就再套个容斥:
然后就没了。
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
47
48
49
50
51
52
53
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(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
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;
}