[PKUWC2018]随机游走

II.[PKUWC2018]随机游走

无脑上minmax容斥。问题转换为求从起点 \(S\) 出发,到达集合 \(\mathbb S\) 中某一点的期望时间。

因为有环,考虑直接爆上高斯消元,时间复杂度 \(O(n^32^n)\)

看上去不太能过?但是这份代码卡常卡得比较优美,加上又没有出菊花图卡,因此跑得飞快,水过去了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
int n,m,S,q,g[18][19],tot,f[1<<18],id[18],s[1<<18];
vector<int>v[18];
void dfs(int x,int fa){
	id[x]=tot++;
	for(auto y:v[x])if(y!=fa&&id[y]!=-2)dfs(y,x);
}
void Gauss_Jordan(){
	for(int i=0;i<tot;i++){
		int j=i;while(!g[j][i])j++;
		if(j!=i)swap(g[i],g[j]);
		int INV=mod-ksm(g[i][i]);
		for(j=0;j<tot;j++)if(j!=i){
			int lam=1ll*g[j][i]*INV%mod;
			for(int k=i;k<tot;k++)g[j][k]=(1ll*lam*g[i][k]%mod+g[j][k])%mod;
			g[j][n]=(1ll*lam*g[i][n]+g[j][n])%mod;
		}
	}
}
int main(){
	scanf("%d%d%d",&n,&q,&S),m=1<<n,S--;
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),x--,y--,v[x].push_back(y),v[y].push_back(x);
	for(int i=1;i<m;i++){
		if(i&(1<<S)){f[i]=0;continue;}
//		printf("%d:\n",i);
		tot=0;
		for(int j=0;j<n;j++)id[j]=(i&(1<<j)?-2:-1);
		dfs(S,-1);
//		for(int j=0;j<n;j++)printf("%d ",id[j]);puts("");
		for(int j=0;j<n;j++){
			if(id[j]<0)continue;
			g[id[j]][id[j]]=g[id[j]][n]=1ll*(mod-1)*v[j].size()%mod;
			for(auto k:v[j])if(id[k]>=0)g[id[j]][id[k]]=1;
		}
//		for(int j=0;j<tot;j++){for(int k=0;k<tot;k++)printf("%d ",g[j][k]);printf(":%d\n",g[j][n]);}
		Gauss_Jordan();
		f[i]=1ll*g[id[S]][n]*ksm(g[id[S]][id[S]])%mod;
//		printf("%d\n",f[i]);
		for(int j=0;j<tot;j++)for(int k=0;k<tot;k++)g[j][k]=0;
	}
	for(int i=0;i<m;i++)s[i]=(__builtin_popcount(i)&1?f[i]:(mod-f[i])%mod);
	for(int i=0;i<n;i++)for(int j=0;j<m;j++)if(j&(1<<i))(s[j]+=s[j^(1<<i)])%=mod;
	for(int i=1,x,y,z;i<=q;i++){
		scanf("%d",&x),z=0;
		while(x--)scanf("%d",&y),z|=(1<<(y-1));
		printf("%d\n",s[z]);
	}
	return 0;
}

现在考虑优化。首先,以题目给出的 \(S\) 为根。然后,考虑令 \(f_x\) 表示从 \(x\) 出发到 \(\mathbb S\) 的期望时间。

假设我们现在已经求出了正确的 \(f\) 值,那么,这些 \(f\) 值有何性质呢?

不妨假设 \(f_x=K_xf_{fa_x}+B_x\),其中 \(fa_x\)\(x\) 的父亲。显然,这一性质在 \(x\notin\mathbb S\) 且是叶子时成立,因为此时 \(f_x=f_{fa_x}+1\)

然后考虑归纳证明。假设对于节点 \(x\),其所有儿子均满足这一性质。

首先,考虑列出传统的转移式 \(f_x=\dfrac{f_{fa_x}+\sum\limits_{y\in\text{son}_x}f_y}{deg_x}+1\)

接着,因为一切 \(y\) 均满足性质,所以其可被表示成

\[f_x=\dfrac{f_{fa_x}+\sum\limits_{y\in\text{son}_x}K_yf_x+B_y}{deg_x}+1 \]

稍微倒腾一下,得到

\[\Big(deg_x-\sum\limits_{y\in\text{son}_x}K_y\Big)f_x=f_{fa_x}+\sum\limits_{y\in\text{son}_x}B_y+deg_x \]

这时,如果我们设 \(k=\sum\limits_{y\in\text{son}_x}K_y,b=\sum\limits_{y\in\text{son}_x}B_y\),就得到

\[f_x=\dfrac1{deg_x-k}f_{fa_x}+\dfrac{deg_x+b}{deg_x-k} \]

于是我们可以愉快地令 \(K_x=\dfrac1{deg_x-k},B_x=\dfrac{deg_x+b}{deg_x-k}\),这样便完成了由儿子的 \(K,B\) 推出父亲的 \(K,B\) 的过程。

而明显,对于 \(x\in\mathbb S\)\(K_x=B_x=0\),因而这部分可以特判掉;对于叶子的 \(x\),上式直接给出了 \(K_x=B_x=1\),可以一样地计算。

我们要求的就是 \(B_S\)。直接dfs一遍完成。

时间复杂度 \(O(n2^n\log n)\),因为还要求逆元。

这类算法被称作树上高斯消元

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
int n,m,S,q,f[1<<18],K[18],B[18],s[1<<18];
vector<int>v[18];
void dfs(int x,int fa,int SS){
	K[x]=B[x]=0;if(SS&(1<<x))return;
	for(auto y:v[x])if(y!=fa)dfs(y,x,SS),(K[x]+=K[y])%=mod,(B[x]+=B[y])%=mod;
	K[x]=ksm(v[x].size()+mod-K[x]),B[x]=1ll*(v[x].size()+B[x])*K[x]%mod;
}
int main(){
	scanf("%d%d%d",&n,&q,&S),m=1<<n,S--;
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),x--,y--,v[x].push_back(y),v[y].push_back(x);
	for(int i=1;i<m;i++)dfs(S,-1,i),f[i]=B[S];
	for(int i=0;i<m;i++)s[i]=(__builtin_popcount(i)&1?f[i]:(mod-f[i])%mod);
	for(int i=0;i<n;i++)for(int j=0;j<m;j++)if(j&(1<<i))(s[j]+=s[j^(1<<i)])%=mod;
	for(int i=1,x,y,z;i<=q;i++){
		scanf("%d",&x),z=0;
		while(x--)scanf("%d",&y),z|=(1<<(y-1));
		printf("%d\n",s[z]);
	}
	return 0;
}

posted @ 2021-04-09 14:17  Troverld  阅读(102)  评论(0编辑  收藏  举报