• [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:
    ",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
    ",g[j][n]);}
    		Gauss_Jordan();
    		f[i]=1ll*g[id[S]][n]*ksm(g[id[S]][id[S]])%mod;
    //		printf("%d
    ",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
    ",s[z]);
    	}
    	return 0;
    }
    

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

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

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

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

    首先,考虑列出传统的转移式 (f_x=dfrac{f_{fa_x}+sumlimits_{yin ext{son}_x}f_y}{deg_x}+1)

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

    [f_x=dfrac{f_{fa_x}+sumlimits_{yin ext{son}_x}K_yf_x+B_y}{deg_x}+1 ]

    稍微倒腾一下,得到

    [Big(deg_x-sumlimits_{yin ext{son}_x}K_yBig)f_x=f_{fa_x}+sumlimits_{yin ext{son}_x}B_y+deg_x ]

    这时,如果我们设 (k=sumlimits_{yin ext{son}_x}K_y,b=sumlimits_{yin ext{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) 的过程。

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

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

    时间复杂度 (O(n2^nlog 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
    ",s[z]);
    	}
    	return 0;
    }
    

  • 相关阅读:
    显式接口实现
    工厂模式总结
    xml操作总结
    抽象类与接口异同
    (转载)将一段符合XML格式规范字符串插入已有XML文档当中
    观察者模式-最终话
    观察者模式
    泛型编程
    迭代器模式
    python学习笔记1 -- 面向对象编程高级编程1
  • 原文地址:https://www.cnblogs.com/Troverld/p/14636926.html
Copyright © 2020-2023  润新知