[LOJ 2542]「PKUWC2018」随机游走

前言

到这里PKUWC2018的题目,斗地主不是题目,就算都补完了,希望今年的PKUWC可以有好运吧。

题目链接

LOJ 2542

做法

显然可以Min-Max容斥,不会的戳这儿
我们要求的是$Max(S)$,所以我们考虑$Min(S)$如何计算。
$Min(S)$实际上求的就是从起点出发,走到S中任意一个点的期望时间。
我们枚举S,以$f_i$表示从i出发走到S中任意一个点的期望时间,$d_i$表示i这个点的度。
如果$i\in S$,那么$f_i=0$,否则$f_i=\frac{\sum\limits_{i\to j}f_j}{d_i}+1$
那么我对这些方程高斯消元,复杂度$2^n*n^3$过大。
发现这个消元实际上是在树上进行,那么我们用树上消元的经典trick来解决。
将$f_i$用$k_i*f_{fa(i)}+b_i$表示,那么我们将原方程变形。
$d_i*f_i=\sum\limits_{j\in son_i}f_j+f_{fa(i)}+d_i$
再将$f(j)$全部用$k_j*f_i+b_j$替换,用$kk$表示$\sum\limits_{j\in son_i}k_j$,$bb$表示$\sum\limits_{j\in son_i}b_j$
那么可以得到$d_i*f_i=kk*f_i+bb+f_{fa(i)}+d_i$
再将$f(i)$用$k_i*f_{fa(i)}+b_i$替换,移项。
$(d_i-kk)*k_i*f_{fa(i)}+(d_i-kk)*b_i=bb+f_{fa(i)}+d_i$
左右两边恒等,这样我们就能解出$k_i$和$b_i$了。
根节点的$f_{fa(i)}$为0,所以根节点$f_i=b_i$,而我们只需要起点的f,所以把起点提到根,做一遍上述的消元就解出了所有的$Min(S)$
由于Min-Max容斥本质上是个带系数的子集卷积。
$Max(S)=\sum\limits_{S’\subseteq S}Min(S’)*(-1)^{|S’|-1}$
如果$|S’|$是偶数的话我们把它的系数变为-1,用$FWT$做一遍子集前缀和就行了,回答变为$O(1)$,而$|S’|$就是$S’.popcount()$
总复杂度$O(2^n*n+Q)$

代码

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
#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<iostream>
#include<cmath>
#define LL long long
#define N (25)
using namespace std;
int n,q,x,tot,now,st,lim,y,zt;
const int P=998244353;
int tow[N<<1],head[N],nxt[N<<1],fa[N],k[N],b[N],d[N],f[N],inv[N],ans[1<<18],pp[1<<18];
template <typename T> void read(T&t) {
t=0;
bool fl=true;
char p=getchar();
while (!isdigit(p)) {
if (p=='-') fl=false;
p=getchar();
}
do {
(t*=10)+=p-48;p=getchar();
}while (isdigit(p));
if (!fl) t=-t;
}
inline void add(int x,int y){
tow[++tot]=y,nxt[tot]=head[x],head[x]=tot;
}
void dfs(int u){
for (int p=head[u];p;p=nxt[p]){
int v=tow[p];
if (v!=fa[u]){
fa[v]=u;
dfs(v);
}
}
}
int Inc(int a,int b){
return (a+b>=P)?(a+b-P):(a+b);
}
int ksm(int a,int b){
int ret=1;
for (;b;b>>=1,a=1ll*a*a%P) if (b&1) ret=1ll*ret*a%P;
return ret;
}
void dfs1(int u){
int kk=0,bb=0;
for (int p=head[u];p;p=nxt[p]){
int v=tow[p];
if (v!=fa[u]){
dfs1(v);
kk=Inc(kk,k[v]),bb=Inc(bb,b[v]);
}
}
if ((1<<(u-1))&now){
k[u]=b[u]=0;
return;
}
kk=1ll*kk*inv[d[u]]%P;
bb=1ll*bb*inv[d[u]]%P;
kk=ksm(Inc(1-kk,P),P-2);
k[u]=1ll*ksm(d[u],P-2)*kk%P;
b[u]=1ll*kk*(bb+1)%P;
}
void FWT(int *f){
lim=1<<n;
for (int i=1;i<lim;i<<=1){
for (int j=0;j<lim;j+=(i<<1)){
for (int k=j;k<j+i;k++){
f[i+k]=Inc(f[k],f[i+k]);
}
}
}
}
int main(){
read(n),read(q),read(st);
inv[0]=inv[1]=1;
for (int i=2;i<=n;i++) inv[i]=Inc(1ll*-P/i*inv[P%i]%P,P);
for (int i=1;i<n;i++){
read(x),read(y);
d[x]++,d[y]++;
add(x,y),add(y,x);
}
dfs(st);
for (now=1;now<(1<<n);now++){
dfs1(st);
ans[now]=b[st];
//printf("test %d %d\n",now,ans[now]);
}
for (int i=1;i<(1<<n);i++){
pp[i]=pp[i>>1]+(i&1);
if (!(pp[i]&1)) ans[i]=-ans[i]+P;
}
FWT(ans);
while (q--){
read(x);
zt=0;
for (int i=1;i<=x;i++) read(y),zt+=(1<<y-1);
printf("%d\n",ans[zt]);
}
return 0;
}