[PKUWC2018]随机游走

II.[PKUWC2018]随机游走

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

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

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

代码:

#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 为根。然后,考虑令 fx 表示从 x 出发到 S 的期望时间。

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

不妨假设 fx=Kxffax+Bx,其中 faxx 的父亲。显然,这一性质在 xS 且是叶子时成立,因为此时 fx=ffax+1

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

首先,考虑列出传统的转移式 fx=ffax+ysonxfydegx+1

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

fx=ffax+ysonxKyfx+Bydegx+1

稍微倒腾一下,得到

(degxysonxKy)fx=ffax+ysonxBy+degx

这时,如果我们设 k=ysonxKy,b=ysonxBy,就得到

fx=1degxkffax+degx+bdegxk

于是我们可以愉快地令 Kx=1degxk,Bx=degx+bdegxk,这样便完成了由儿子的 K,B 推出父亲的 K,B 的过程。

而明显,对于 xSKx=Bx=0,因而这部分可以特判掉;对于叶子的 x,上式直接给出了 Kx=Bx=1,可以一样地计算。

我们要求的就是 BS。直接dfs一遍完成。

时间复杂度 O(n2nlogn),因为还要求逆元。

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

代码:

#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 @   Troverld  阅读(108)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示