PKUSC2018 随机游走

PKUSC2018 随机游走

一道融合了较多知识点、但都不是很深的题。(细节顿号)

前置芝士

树上高斯消元,\(min-max\)容斥,\(FMT\)(俗称高位前缀和?)
树上高斯消元比较简单,不多赘述
\(FMT\)好像有很深奥的原理,但是高位前缀和逐位加入贡献的理解完全够用
所以展开叙述一下\(min-max\)容斥

min-max容斥

这种解法主要来源于两条显然的式子

\[min(S)=\sum_{T \subseteq S} (-1)^{|T|-1} \times max(T) \]

\[max(S)=\sum_{T \subseteq S} (-1)^{|T|-1} \times min(T) \]

下面给出一种抽象证明
对于\(min(S)\) 在计算式子右侧时 仅会出现一次 即\(max(S)==T\)
对于其他的 \(max(T)\),必然可以找到一个小于 \(max(T)\)的数
在一种方案中被取,另一种方案中不被取,而这两个方案中其他数的取舍方式相同
于是这两种方式的\(max\)相同,而其中取得数个数不同,即容斥系数互为相反数,就抵消了

Solve

看完题意,直接计算\(max\)显然很困难,但是通过 式\(2\) 就可以转化为 计算\(min\)
对于一次游走,只要碰到\(S\)中的一个点就可以直接结束,显然简单的多

同时问题就转化为了一个可以用树上高斯消元的较简单的过程
再观察到 \(min-max\)容斥的两条式子都是对子集求和,考虑用到\(FWT\)快速求和

代码环节

#include<bits/stdc++.h>
using namespace std;

#define Mod(x) (x>=P)&&(x-=P)||(x<0)&&(x+=P)
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
#define erep(i,a) for(int i=hd[a];i;i=nxt[i])

typedef long long ll;
void Max(int &x,int y){(x<y)&&(x=y);}
void Min(int &x,int y){(x>y)&&(x=y);}

bool vio;
char IO;
int rd(int res=0){
	bool f=0;
	while(IO=getchar(),IO<48||IO>57)
		f|=IO=='-';
	do res=(res<<1)+(res<<3)+(IO^48);
	while(IO=getchar(),isdigit(IO));
	return f?-res:res;
}
const int P=998244353;
const int M=1e6+10;
int nxt[M],hd[M],to[M],deg[M],ecnt;
int f[M],A[20],B[20],pc[M];
int n,q,rt,T;
void Add(int a,int b){
	nxt[++ecnt]=hd[a],to[hd[a]=ecnt]=b;
	++deg[a];
}
int Pow(int a,int b){
	int res=1;
	for(Mod(a);b;b>>=1,a=1ll*a*a%P)
		if(b&1)res=1ll*res*a%P;
	return res;
}
void dfs(int x,int f){
	if(T>>(x-1)&1)return ;
	int sumA=0,sumB=0;
	erep(i,x){
		int y=to[i];
		if(y==f)continue;
		dfs(y,x);
		sumA+=A[y],Mod(sumA);
		sumB+=B[y],Mod(sumB);
	}
	int tmp=Pow(deg[x]-sumA,P-2);
	A[x]=tmp;
	B[x]=1ll*(deg[x]+sumB)*tmp%P;
}
bool let;
int main(){
	cerr<<(&vio-&let)/1024.0/1024<<endl;
	n=rd(),q=rd(),rt=rd();
	rep(i,2,n){
		int a=rd(),b=rd();
		Add(a,b),Add(b,a);
	}
	int S=(1<<n)-1;
	rep(i,1,S){
		memset(A,0,sizeof A);
		memset(B,0,sizeof B);
		T=i,dfs(rt,0);
		pc[i]=pc[i>>1]+(i&1);
		f[i]=(pc[i]&1?1:-1)*B[rt],Mod(f[i]);
	}
	for(int i=1;i<S;i<<=1)
		for(int j=0;j<S;j+=i*2)
			rep(k,j,j+i-1)f[k+i]+=f[k],Mod(f[k+i]);
	while(q--){
		int s=0;
		rep(i,1,rd())s|=1<<(rd()-1);
		printf("%d",f[s]);
	}
	return 0;
}

原谅我的懒人开数组法

posted @ 2021-06-08 20:30  Saltywater  阅读(63)  评论(0)    收藏  举报