「PKUWC2018」随机游走
显然你如果直接带一个子集到树上dp的话复杂度是会炸上天的23333.
考虑期望也是可以进行min_max容斥的,也就是: max{S} = ∑ min{T} * (-1) ^( |T|+1 ) ,其中T是S的一个非空子集,max{S}和min{S}分别代表集合中所有条件都被满足的期望时间 和 集合中至少有一个条件被满足的期望时间, 当然对本题来说就是 所有钦定的点都被到过一次的期望时间 和 第一次到某个钦定的点的期望时间。。。。
发现min非常的好算,对于每个集合直接一次树上dp O(n * log 998244351) 就可以算出来了(千万不要用高斯消元。。。这种树上路径期望问题可以把每个节点的dp值表示 a * f(fa) + b的形式,dfs的时候直接算就行了)
然后把每个集合的min{}算出来之后就可以用类FMT的逆推方法推出每个集合的答案。
不过两者之间不同的是,FMT的式子是: f '(S') = ∑ f(S) * (-1) ^ ( |S'| - |S|) ,而这个的是 f '(S') = ∑ f(S) * (-1) ^ ( |S|+1 )。
但其实并没有什么难度,因为我们把 S' 中1的个数按奇偶性讨论一下就可以直接在FMT之后的数组上修改成我们要的样子了。。。
#include<bits/stdc++.h> #define ll long long using namespace std; #define pb push_back const int ha=998244353,N=300005; inline int read(){ int x=0; char ch=getchar(); for(;!isdigit(ch);ch=getchar()); for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; return x-1; } inline int add(int x,int y){ x+=y; return x>=ha?x-ha:x;} inline void ADD(int &x,int y){ x+=y; if(x>=ha) x-=ha;} int ksm(int x,int y){ int an=1; for(;y;y>>=1,x=x*(ll)x%ha) if(y&1) an=an*(ll)x%ha; return an; } vector<int> g[23]; int n,Q,root,ans[N],a[23],b[23],S,d[23]; bool bt[N]; void dfs(int x,int fa){ a[x]=d[x],b[x]=1; int tmp=1; for(int i:g[x]) if(i!=fa){ dfs(i,x),ADD(tmp,ha-a[i]*(ll)d[x]%ha); ADD(b[x],b[i]*(ll)d[x]%ha); } tmp=ksm(tmp,ha-2); a[x]=a[x]*(ll)tmp%ha; b[x]=b[x]*(ll)tmp%ha; if((1<<x)&S) a[x]=b[x]=0; } inline void solve(){ bt[0]=1; for(int i=1;i<(1<<n);i++) bt[i]=!bt[i^(i&-i)]; for(int i=0;i<n;i++) for(int j=(1<<n)-1;j>=0;j--) if(!((1<<i)&j)) ADD(ans[j|(1<<i)],ha-ans[j]); for(int i=(1<<n)-1;i>=0;i--) if(bt[i]) ans[i]=ha-ans[i]; for(int k,s;Q;Q--){ scanf("%d",&k),s=0; while(k--) s|=1<<read(); printf("%d\n",ans[s]); } } int main(){ scanf("%d%d",&n,&Q),root=read(); int uu,vv; for(int i=1;i<n;i++){ uu=read(),vv=read(); g[uu].pb(vv),g[vv].pb(uu); d[uu]++,d[vv]++; } for(int i=0;i<n;i++) d[i]=ksm(d[i],ha-2); for(S=1;S<(1<<n);S++){ memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); dfs(root,-1),ans[S]=b[root]; } solve(); return 0; }
我爱学习,学习使我快乐