loj2542 「PKUWC2018」隨機遊走 min-max容斥證明
阿新 • • 發佈:2018-11-12
題目描述
給定一棵 n 個結點的樹,你從點 x 出發,每次等概率隨機選擇一條與所在點相鄰的邊走過去。
有 Q 次詢問,每次詢問給定一個集合 S,求如果從 x 出發一直隨機遊走,直到點集 S 中所有點都至少經過一次的話,期望遊走幾步。
特別地,點 x(即起點)視為一開始就被經過了一次。
答案對 998244353 取模。
輸入格式
第一行三個正整數 n,Q,x。
接下來 n-1 行,每行兩個正整數 (u,v) 描述一條樹邊。
接下來 Q 行,每行第一個數 k 表示集合大小,接下來 k 個互不相同的數表示集合 S。
輸出格式
輸出 Q行,每行一個非負整數表示答案。
樣例輸入
3 5 1
1 2
2 3
1 1
1 3
2 2 3
3 1 2 3
2 1 2
樣例輸出
0
4
4
4
1
題解
首先由min-max容斥
證明:
考慮列舉每一個值作為最小值出現,即將S元素從小到大排序後某個元素為出現的第一個元素:
同時
右邊組合數就是以s[i]為最小值的子集個數,(-1)的冪就是(-1)的子集大小+1次方
只有當
時右邊的求和才為1,所以最終就是最大值。
只要你是對同一種東西求最大值和最小值,這個式子就是對的。
這題中,將詢問集合最後到達的點的期望步數設為最大值,集合最先到達的點的期望步數設為最小值,它顯然是滿足這個式子的。
那麼求從x到集合s內最先到達的點的期望步數設為
顯然如果x屬於s,那麼
否則就想正常的期望一樣,設y為與x相連的點,d[x]為點的度數,
設
。
如果x屬於s的話,k和b就都等於0。
然後自k和b都等於0的點向上推就好了。
程式碼
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 20
#define M 262150
#define mo 998244353
#define ll long long
using namespace std;
int n,Q,S,las[N],nxt[N*2],to[N*2],fa[N],d[N],tot=1,X,e[N],num[M];
ll f[M],K[N],B[N];
ll mi(ll a,ll b)
{
ll c=1;
for(;b;b/=2,a=a*a%mo) if(b%2==1) c=c*a%mo;
return c;
}
void putin(int x,int y)
{
nxt[++tot]=las[x];las[x]=tot;to[tot]=y;
}
void dg(int x)
{
if(e[x-1]&S)
{
K[x]=B[x]=0;
return;
}
K[x]=B[x]=d[x];
for(int i=las[x];i;i=nxt[i])
{
int y=to[i];
if(y==fa[x]) continue;
fa[y]=x;
dg(y);
B[x]=(B[x]+B[y])%mo;
K[x]=(K[x]-K[y]+mo)%mo;
}
K[x]=mi(K[x],mo-2)%mo;
B[x]=B[x]*K[x]%mo;
}
int main()
{
scanf("%d%d%d",&n,&Q,&X);
fo(i,2,n)
{
int x,y;scanf("%d%d",&x,&y);
putin(x,y);putin(y,x);
d[x]++;d[y]++;
}
e[0]=1;fo(i,1,n) e[i]=e[i-1]*2;
for(S=1;S<=e[n]-1;S++)
{
num[S]=num[S>>1]+S&1;
dg(X);
f[S]=B[X];
}
while(Q--)
{
int m,s=0,x;scanf("%d",&m);
fo(i,1,m) scanf("%d",&x),s=s|e[x-1];
ll ans=0;
fo(i,1,e[n]-1) if((i&s)==i) ans=(ans+(num[i]%2==1?1:-1)*f[i]+mo)%mo;
printf("%lld\n",ans);
}
}