2017 Multi-University Training Contest - Team 5

2017 Multi-University Training Contest - Team 5


1001 - Rikka with Candies

题意

给定长为n的序列A和长为m的序列B,(n <= 50000,m<=50000),和q(q<=50000)个询问,每次给定k,询问有多少对(i, j)满足A_i % B_j = k,(1<=i<=n,1<=j<=m,B_j>k),输出数量%2的值。

题解

  • 问题等价于求(A_i - k) % B_j = 0的对数,那么我们可以从大到小枚举B_j,每次更新其倍数x,令f[x]表示有多少个B_j是其因子,那么我们只需要对每个A_i,令ans += f[A_i-k],在更新前对k进行求解,这样保证了每个x得来的贡献都是满足要求的。
  • 考虑到只需要判断奇偶性,使用bitset来优化,对每一个i,枚举其倍数,复杂度是O(nlogn),对k求解是$O(\frac{n^2}{32})$。

代码

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
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 5;
void read(int& res) {
char c;
while (!isdigit(c = getchar()));
res = c - '0';
while (isdigit(c = getchar())) res = res * 10 + c - '0';
}
int k[N], ans[N];
bool visb[N], visk[N];
bitset<N> a, x;
int main() {
int t;
read(t);
while (t--) {
int n, m, q, ma = 0, va;
read(n); read(m); read(q);
for (int i = 1; i <= n; i++) read(va), a.set(va);
for (int i = 1; i <= m; i++) {
read(va);
ma = max(ma, va);
visb[va] = 1;
}
for (int i = 1; i <= q; i++) { read(k[i]); visk[k[i]] = 1; }
for (int i = ma; ~i; i--) {
if (visk[i]) ans[i] = (x & (a >> i)).count() & 1;
if (visb[i]) for (int j = 0; j <= ma; j += i) x.flip(j);
}
for (int i = 1; i <= q; i++) printf("%d\n", ans[k[i]]);
if (t) {
for (int i = 0; i <= ma; i++) visb[i] = visk[i] = 0;
a.reset(); x.reset();
}
}
return 0;
}

1002 - Rikka with String

题意

有多少个长为2L的串S,包含给定的n个子串,且满足S[i]≠S[|S|−i+1]

  • 1≤n≤6,1≤L≤100,1≤|si|≤20

题解

  • 把n个串和n个串反转后加入AC自动机。
  • 考虑dp[i][j][s]表示前i个字符中,当前在AC自动机的状态为j,且包含给定n个串的状态为s的方案数。
  • 但是在L和L+1突然连起来的地方会形成新的串,那么暴力枚举哪些状态在L和L+1的地方连起来后会形成给定的串,也把这些子串加入AC自动机,所以ac自动机要设置两个val值,且其中一个val值在i==L的时候才有效。

代码

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<iostream>
using namespace std;
typedef long long ll;
#define mod 998244353
#define maxn 55
const int maxnode=555;
const int sigma_size=2;
struct AC_Automata {
int ch[maxnode][sigma_size];
int val[maxnode];
int en[maxnode];
int f[maxnode];
int sz;
void init() {
sz=1;
memset(ch[0],0,sizeof(ch[0]));
val[0]=0;
en[0]=0;
}
void Insert(char *s,int n,int v,bool ty) {
int u=0;
for(int i=0; i<n; i++) {
int id=s[i]-'0';
if(ch[u][id]==0) {
ch[u][id]=sz;
memset(ch[sz],0,sizeof(ch[sz]));
en[sz]=0;
val[sz++]=0;
}
u=ch[u][id];
}
if(ty) val[u]|=1<<v;
else en[u]|=1<<v;
}
void getFail() {
queue<int> q;
f[0]=0;
for(int i=0; i<sigma_size; i++) {
int u=ch[0][i];
if(u) {
f[u]=0;
q.push(u);
}
}
while(!q.empty()) {
int r=q.front();
q.pop();
for(int i=0; i<sigma_size; i++) {
int u=ch[r][i];
if(u==0) {
ch[r][i]=ch[f[r]][i];
continue;
}
q.push(u);
int v=f[r];
while(v && ch[v][i]==0) v=f[v];
f[u]= ch[v][i];
val[u] |= val[f[u]];
en[u] |= en[f[u]];
}
}
}
};
AC_Automata ac;
int dp[110][maxnode][1<<7];
char s[maxn];
char ss[maxn];
int main() {
int ca;
// freopen("data.txt","r",stdin);
// freopen("ans.txt","w",stdout);
scanf("%d",&ca);
while(ca--){
int n,L;
scanf("%d%d",&n,&L);
memset(dp,0,sizeof(dp));
ac.init();
for(int i=0;i<n;i++){
scanf("%s",s);
int len=strlen(s);
ac.Insert(s,len,i,true);
for(int j=len-1;j>=(len+1)/2;j--){
bool flag=true;
for(int k=0;k<len-j;k++){
if(s[j+k]==s[j-k-1]){
flag=false;break;
}
}
if(flag){
ac.Insert(s,j,i,false);
}
}
for(int j=(len+1)/2-1;j>0;j--){
bool flag=true;
int o=0;
for(int k=0;k<j;k++){
if(s[j-k-1]==s[j+k]){
flag=false;break;
}
}
if(flag){
for(int k=len-1;k>=j;k--)
ss[o++]=s[k]=='0'?'1':'0';
ac.Insert(ss,len-j,i,false);
}
}
reverse(s,s+len);
for(int j=0;j<len;j++)
s[j]=s[j]=='0'?'1':'0';
ac.Insert(s,len,i,true);
}
ac.getFail();
int u=ac.ch[0][1];
dp[0][0][0]=1;
for(int i=0;i<L;i++){
for(int j=0;j<ac.sz;j++){
for(int s=0;s<1<<n;s++){
for(int k=0;k<2;k++){
int u=ac.ch[j][k];
int ns=s|ac.val[u];
if(i==L-1) ns|=ac.en[u];
dp[i+1][u][ns]+=dp[i][j][s];
dp[i+1][u][ns]%=mod;
}
}
}
}
int ans=0;
for(int i=0;i<ac.sz;i++){
ans=(ans+dp[L][i][(1<<n)-1])%mod;
}
printf("%d\n",ans);
}
return 0;
}
/*
3
3 3
01
00
01
3 2
1
101
1
1 3
0
*/

1004 - Rikka with Candies

题意

Alice和Bob玩石头剪刀布,每个人都是随机出石头或者剪刀或者步,三者概率相同,总共玩n局游戏,如果最后Alice赢a场,Bob赢b场,那么这局游戏的分数s是gcd(a, b),求$s * 3^{2n}$的期望值。

题解

  • Alice赢a场,Bob赢b场的期望值是${n \choose a} * {n-a \choose b} * 3^n$,那么$$ans = 3^n * \sum_{i=0}^{n} \sum_{j=0}^{n-i} {n \choose a} * {n-a \choose b} * gcd(i, j) $$
  • $$= 3^n * \sum_{i=0}^{n} \sum_{j=0}^{n-i} {n \choose a} * {n-a \choose b} * \sum_{d|gcd(i,j)} {φ(d)}$$
  • 交换求和顺序我们得到$$ans = 3^n * \sum_{d=0}^{n} φ(d)\sum_{i=0}^{\frac{n}{d}} \sum_{j=0}^{\frac{n}{d}-i} {n \choose id} * {n-id \choose jd}$$
  • $$ = \frac{3^n}{n!} * \sum_{d=1}^{n} φ(d)\sum_{i=0}^{\frac{n}{d}} \frac{1}{(n-id)!} \sum_{j=0}^{i} \frac{1}{(jd)!} * \frac{1}{((i-j)d)!}$$
  • 注意到后面的求和是一个卷积,使用FFT进行优化,那么总复杂度就是$\sum_{i=1}^{n}O(\frac{n}{i}logn) = O(n{logn}^2)$,上面的分析过程多考虑了i和j同时为0的情况,最后需要减去多余的φ(d),对于任意质数为模数的FFT需要考虑到实现时的效率,而且在次数界很小时直接暴力求卷积是更优的。

代码

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
114
115
116
117
118
119
120
121
122
123
124
125
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 1e5 + 5, FFT_MAX = 1 << 18, N = FFT_MAX << 1;
const double PI = acos(-1.0);
int n, mod, pri[MAXN], phi[MAXN], fac[MAXN], inv[MAXN];
struct comp {
double a, b;
comp(double _a, double _b) : a(_a), b(_b) {}
comp() {}
comp operator + (const comp& y) const { return comp(a + y.a, b + y.b); }
comp operator - (const comp& y) const { return comp(a - y.a, b - y.b); }
comp operator * (const comp& y) const { return comp(a * y.a - b * y.b, a * y.b + b * y.a); }
comp operator ! ()const { return comp(a, -b); }
} tmp[FFT_MAX + 1];
void add(int& x, LL y) {
x = (x + y) % mod;
if (x < 0) x += mod;
}
namespace fft{
comp f[N], g[N], t[N];
int bitrev[FFT_MAX];
void init() {
int L = 18;
bitrev[0] = 0;
for (int i = 1; i < FFT_MAX; i++) bitrev[i] = bitrev[i >> 1] >> 1 | ((i & 1) << (L - 1));
for (int i = 0; i <= FFT_MAX; i++) tmp[i] = comp(cos(2 * PI / FFT_MAX * i), sin(2 * PI / FFT_MAX * i));
}
void dft(comp* a, int n, int on = 1) {
int d = 0;
while ((1 << d) * n != FFT_MAX) d++;
for (int i = 0; i < n; i++) if (i < (bitrev[i] >> d)) swap(a[i], a[bitrev[i] >> d]);
for (int l = 2; l <= n; l <<= 1) {
int del = FFT_MAX / l * on;
for (int i = 0; i < n; i += l) {
comp *le = a + i, *ri = a + i + (l >> 1), *w = on == 1 ? tmp : tmp + FFT_MAX;
for (int k = 0; k < l >> 1; k++) {
comp ne = *ri * *w;
*ri = *le - ne, *le = *le + ne;
le++, ri++, w += del;
}
}
}
if (on != 1) for (int i = 0; i < n; i++) a[i].a /= n, a[i].b /= n;
}
void convol(int* x, int* y, int* q, int n) {
int L = n * 2 + 1;
if (n <= 60) {
for (int i = 0; i < L; i++) q[i] = 0;
for (int i = 0; i <= n; i++) for (int j = 0; j <= n; j++) add(q[i + j], (LL)x[i] * y[j]);
return;
}
int N = 2;
while (N < L) N <<= 1;
for (int i = 0; i < N; i++) {
if (i <= n) f[i] = comp(x[i] >> 15, x[i] & 32767), g[i] = comp(y[i] >> 15, y[i] & 32767);
else f[i] = g[i] = comp(0, 0);
}
dft(f, N); dft(g, N);
for (int i = 0; i < N; i++) {
int j = i ? N - i : 0;
t[i] = ((f[i] + !f[j]) * (!g[j] - g[i]) + (!f[j] - f[i]) * (g[i] + !g[j])) * comp(0, 0.25);
}
dft(t, N, -1);
for (int i = 0; i < L; i++) q[i] = (LL(t[i].a + 0.5) % mod << 15) % mod;
for (int i = 0; i < N; i++) {
int j = i ? N - i : 0;
t[i] = (!f[j] - f[i]) * (!g[j] - g[i]) * comp(-0.25, 0) + comp(0, 0.25) * (f[i] + !f[j]) * (g[i] + !g[j]);
}
dft(t, N, -1);
for (int i = 0; i < L; i++) add(q[i], LL(t[i].a + 0.5) + (LL(t[i].b + 0.5) % mod << 30));
}
};
void init() {
phi[1] = 1;
int k = 0;
for (int i = 2; i <= 100000; i++) {
if (!phi[i]) pri[k++] = i, phi[i] = i - 1;
for (int j = 0; j < k; j++) {
int s = i * pri[j];
if (s > 100000) break;
if (i % pri[j] == 0) { phi[s] = phi[i] * pri[j]; break; }
phi[s] = phi[i] * (pri[j] - 1);
}
}
}
int qpow(int x, int n) {
int res = 1;
while (n) {
if (n & 1) res = (LL)res * x % mod;
x = (LL)x * x % mod;
n >>= 1;
}
return res;
}
int x1[MAXN+5], x2[MAXN+5], q[MAXN*2+5];
void solve() {
fac[0] = fac[1] = 1;
for (int i = 2; i <= n; i++) fac[i] = (LL)fac[i-1] * i % mod;
inv[n] = qpow(fac[n], mod - 2);
for (int i = n; i; i--) inv[i-1] = (LL)inv[i] * i % mod;
int ans = 0;
for (int d = 1; d <= n; d++) {
int k = n / d;
for (int i = 0; i <= k; i++) x1[i] = x2[i] = inv[i*d];
fft::convol(x1, x2, q, k);
int num = 0;
for (int i = 0; i <= k; i++) num = (num + (LL)q[i] * inv[n-i*d] % mod) % mod;
ans = (ans + (LL)num * phi[d] % mod) % mod;
}
ans = (LL)ans * fac[n] % mod;
for (int i = 1; i <= n; i++) ans = (ans - phi[i] + mod) % mod;
printf("%d\n", (LL)ans * qpow(3, n) % mod);
}
int main() {
fft::init(); init();
int t;
scanf("%d", &t);
while (t--) { scanf("%d %d", &n, &mod); solve(); }
return 0;
}

1007 - Rikka with Match

题意

给一颗树,去掉树上的一些边后求最大匹配,问有多少种去边方法使得最大匹配数是m的倍数(包括0)。

题解

  • 树形dp。
  • 设两个数组f[i][j]和g[i][j],f[i][j]表示i节点与其某一个子节点存在一条在最大匹配中的边,j表示该最大匹配模以m后为j。
    g[i][j]表示i节点与其任意一个子节点不存在一条在最大匹配中的边,j表示该最大匹配模以m后为j。
  • 那么假设f[i][j]和g[i]j都已经求得,有一颗子树k,f[k][j]和g[k][j]也已经求得。现在将子树K加到i节点下方成为i节点的子节点。那么变换后的f'[i][j]和g'[i][j]可以通过地推式得到:
    f'[i][(j+l+1)%m] += f[i][j] * (f[k][l] + g[k][l]) * 2 + g[i][j] * g[k][l]
    g'[i][(j+l)%m] += g[i][j] * (f[k][l] + g[k][l]) + g[i][j] * f[k][l]
  • 可以这样理解,先算f'[i][j]。如果i和k之间的边是去掉的边,那么增加的个数就是f[i][j]*(g[k][l] + f[k][l])。
  • 如果不是去掉的边,分两种情况考虑。Ik是最大匹配中的边,那么原来i节点没有最大匹配中的边,原来k节点也要没有最大匹配中的边,所以增加的个数是g[i][j]*g[k][l]。
  • 最后ik不是最大匹配中的边所以增加了f[i][j]*(g[k][l]+f[k][l])。再算g'[i][j]。如果ik之间的边是去掉的,那么增加的个数是g[i][j]*(f[k][l]+g[k][l])。如果ik之间的边没有去掉,为了使这条边不是最大匹配中的边k节点就要已经是最大匹配中的点,所以增加的个数是g[i][j]*f[k][l]。

代码

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
#include <bits/stdc++.h>
using namespace std;
#define mod 998244353
#define maxn 500010
int n,m;
struct P{
int to,nxt;
}edge[maxn*2];
int head[maxn],cn;
int f[maxn][210],g[maxn][210],sz[maxn];
int A[410],B[410];
void calc(int x,int y){
memset(A,0,sizeof(A));
memset(B,0,sizeof(B));
for (int i=0;i<=sz[x];i++){
for (int j=0;j<=sz[y];j++){
A[i+j]=(A[i+j]+2ll*f[x][i]*(1ll*f[y][j]+g[y][j])%mod+1ll*g[x][i]*g[y][j]%mod)%mod;
B[i+j]=(B[i+j]+1ll*g[x][i]*(2ll*f[y][j]+g[y][j])%mod)%mod;
}
}
for (int i=0;i<m;i++){
f[x][i]=(A[i]+A[i+m])%mod;
g[x][i]=(B[i]+B[i+m])%mod;
}
sz[x]=min(sz[x]+sz[y],m-1);
}
void dfs(int x,int fa){
memset(f[x],0,sizeof(f[x]));
memset(g[x],0,sizeof(g[x]));
g[x][0]=1,sz[x]=0;
for (int i=head[x];~i;i=edge[i].nxt){
int u=edge[i].to;
if (u==fa) continue;
dfs(u,x);
calc(x,u);
}
sz[x]=min(sz[x]+1,m-1);
for (int i=m;i;i--) f[x][i]=f[x][i-1];
f[x][0]=f[x][m];
// printf("%d:\n",x);
// for (int i=0;i<m;i++) printf("%d ",f[x][i]);printf("\n");
// for (int i=0;i<m;i++) printf("%d ",g[x][i]);printf("\n");
}
void add(int x,int y){
edge[cn].to=y,edge[cn].nxt=head[x],head[x]=cn++;
edge[cn].to=x,edge[cn].nxt=head[y],head[y]=cn++;
}
int main(){
int t,x,y;
// freopen("1007.in","r",stdin);
scanf("%d",&t);
while (t--){
memset(head,-1,sizeof(head));
cn=0;
scanf("%d %d",&n,&m);
for (int i=0;i<n-1;i++){
scanf("%d %d",&x,&y);
add(x,y);
}
dfs(1,-1);
printf("%lld\n",(1ll*f[1][0]+g[1][0])%mod);
}
return 0;
}