前言
这是学习容斥过程中的一个比较裸的题了
题意简介
题目大意
给出一棵
n
n
n个点的树,给出树上的一个点
x
x
x
现在进行
Q
Q
Q次询问,每次询问一个点集,求从
x
x
x点开始进行随机随机游走,第一次走遍这个点集的期望步数
(随机游走即每次等概率走到一个与自己相邻的点)
数据范围
1 ≤ n ≤ 18 , 1 ≤ Q ≤ 5000 1\le n\le18,1\le Q\le 5000 1≤n≤18,1≤Q≤5000
前置知识(最值反演 Min-Max容斥)
m
a
x
{
S
}
=
∑
T
⊆
S
(
−
1
)
∣
T
∣
+
1
m
i
n
{
T
}
max\{S\}=\sum_{T\subseteq S}(-1)^{|T|+1}min\{T\}
max{S}=T⊆S∑(−1)∣T∣+1min{T}
具体介绍可以看我的博客
介绍链接
这个式子套上期望依然成立
题解
所有点第一次期望访问的步数的集合
S
S
S,
m
a
x
{
S
}
max\{S\}
max{S}就是每个点都被走过的期望步数,
m
i
n
{
S
}
min\{S\}
min{S}代表里任意一个点被第一次访问的期望时间
我们发现我们现在要求的就是
m
a
x
{
S
}
max\{S\}
max{S}
但是直接求不好求,所以用Min-Max容斥转化成求
m
i
n
{
S
}
min\{S\}
min{S}
我们可以预处理
m
i
n
{
S
}
min\{S\}
min{S},并且
Θ
(
3
n
)
\Theta(3^n)
Θ(3n)枚举子集计算答案
那么现在就是考虑如何求
m
i
n
{
S
}
min\{S\}
min{S}
这个似乎我当时在考场上就推出来了
考虑对于一个集合
S
S
S,设
f
(
x
)
f(x)
f(x)为从
x
x
x点开始随机游第一次访问集合
S
S
S的期望步数
然后分类讨论
若
x
∈
S
x\in S
x∈S
f
(
x
)
=
0
f(x)=0
f(x)=0
若
x
∉
S
x\notin S
x∈/S
f
(
x
)
=
1
+
1
d
x
∑
e
d
g
e
(
x
,
y
)
f
(
y
)
f(x)=1+\frac{1}{d_x}\sum_{edge(x,y)}f(y)
f(x)=1+dx1edge(x,y)∑f(y)
其中
d
x
d_x
dx为
x
x
x的度数,
e
d
g
e
(
x
,
y
)
edge(x,y)
edge(x,y)代表
x
,
y
x,y
x,y间有边
对于
x
∉
S
x\notin S
x∈/S的部分随便定一个根
r
o
o
t
∈
S
root\in S
root∈S
我们发现对于
x
x
x是叶子节点的情况:
f
(
x
)
=
f
(
f
a
x
)
+
1
f(x)=f(fa_x)+1
f(x)=f(fax)+1
我们发现对于
x
x
x是非子节点的情况:
f
(
x
)
=
1
+
1
d
x
∑
y
=
s
o
n
x
f
(
y
)
+
1
d
x
f
(
f
a
x
)
f(x)=1+\frac{1}{d_x}\sum_{y=son_x}f(y)+\frac{1}{d_x}f(fa_x)
f(x)=1+dx1y=sonx∑f(y)+dx1f(fax)
我们设一个节点
x
x
x的答案为
(
a
,
b
)
(a,b)
(a,b)代表:
f
(
x
)
=
a
+
b
f
(
f
a
x
)
f(x)=a+bf(fa_x)
f(x)=a+bf(fax)
我们发现,对于任何一个节点的答案
(
a
,
b
)
(a,b)
(a,b),一定满足
b
≤
1
b\le 1
b≤1(实数意义下)
(由于
r
o
o
t
∈
S
root\in S
root∈S,所以不会有枚举的节点没有
f
a
fa
fa的情况)
所以对于非叶子节点的
x
x
x,等号右侧的
f
(
x
)
f(x)
f(x)的系数一定小于
1
1
1,一项即可算出当前的的答案
(
a
,
b
)
(a,b)
(a,b)
算完答案
(
a
,
b
)
(a,b)
(a,b)后,即可从根倒着推回去
非常方便
这样算出来复杂度是
Θ
(
3
n
)
\Theta(3^n)
Θ(3n)的,需要卡常,我写了一波被卡成70
这个时候就需要优化瓶颈
发现是裸的FMT,写一发就好了
复杂度
Θ
(
2
n
n
l
o
g
n
)
\Theta(2^nnlogn)
Θ(2nnlogn)
代码
#include<cstdio>
#include<cctype>
namespace fast_IO
{
const int IN_LEN=10000000,OUT_LEN=10000000;
char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define getchar() getchar_()
#define putchar(x) putchar_((x))
//#include<ctime>
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
//template <typename T> inline void swap(T*a,T*b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
char cu=getchar();x=0;bool fla=0;
while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
if(x>=10)printe(x/10);
putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
if(x<0)putchar('-'),printe(-x);
else printe(x);
}
const LL mod=998244353;
inline LL pow(LL x,LL y)
{
LL res=1;
for(;y;y>>=1,x=x*x%mod)if(y&1)res=res*x%mod;
return res;
}
int n,q,x,k;
int bit[20],inv[20],d[20];
int head[20],nxt[40],tow[40],tmp;
inline void addb(const int u,const int v)
{
tmp++;
nxt[tmp]=head[u];
head[u]=tmp;
tow[tmp]=v;
}
int zt;
struct Ans
{
int a,b;
inline Ans operator +(const Ans&y)const{return (Ans){a+y.a,b+y.b};}
}Q[20];
LL ans[20];
void dfs1(const int u,const int fa)
{
if(zt&bit[u])
{
for(rg int i=head[u];i;i=nxt[i])
{
const int v=tow[i];
if(v==fa)continue;
dfs1(v,u);
}
Q[u].a=Q[u].b=0;
}
else
{
const LL INV=inv[d[u]];
Q[u]=(Ans){1,INV};LL xs=1;
for(rg int i=head[u];i;i=nxt[i])
{
const int v=tow[i];
if(v==fa)continue;
dfs1(v,u);
xs=(xs+mod-INV*Q[v].b%mod)%mod;
Q[u].a=(Q[u].a+Q[v].a*INV)%mod;
}
const LL INTO=pow(xs,mod-2);
Q[u].a=INTO*Q[u].a%mod;
Q[u].b=INTO*Q[u].b%mod;
}
}
void dfs2(const int u,const int fa)
{
if(zt&bit[u])
{
ans[u]=0;
for(rg int i=head[u];i;i=nxt[i])
{
const int v=tow[i];
if(v==fa)continue;
dfs2(v,u);
}
}
else
{
ans[u]=(Q[u].a+ans[fa]*Q[u].b)%mod;
for(rg int i=head[u];i;i=nxt[i])
{
const int v=tow[i];
if(v==fa)continue;
dfs2(v,u);
}
}
}
int res[524288];
int pc[524288];
int main()
{
bit[1]=1;
for(rg int i=2;i<=19;i++)bit[i]=bit[i-1]<<1;
inv[1]=1;for(rg int i=2;i<=19;i++)inv[i]=(LL)(mod-mod/i)*inv[mod%i]%mod;
read(n),read(q),read(x);
for(rg int i=1;i<n;i++)
{
int u,v;read(u),read(v);
addb(u,v),addb(v,u);
d[u]++,d[v]++;
}
for(zt=1;zt<bit[n+1];zt++)
{
pc[zt]=pc[zt>>1]^(zt&1);
for(rg int i=1;i<=n;i++)
if(zt&bit[i])
{
dfs1(i,i);
dfs2(i,i);
break;
}
if(pc[zt]&1)res[zt]=ans[x];
else res[zt]=(mod-ans[x])%mod;
}
for(rg int i=1;i<bit[n+1];i<<=1)
for(rg int j=0;j<bit[n+1];j++)
if(i&j)
res[j]=(res[j]+res[j^i])%mod;
while(q--)
{
read(k),zt=0;
while(k--)read(x),zt|=bit[x];
print(res[zt]),putchar('\n');
}
return flush(),0;
}
不知为何分类讨论使得程序拉的特别长
总结
min-max容斥很重要,使得整题好做了很多
然后这算是FMT的应用吧,清真好题