[PKUWC2018]随机游走
II.[PKUWC2018]随机游走
无脑上minmax容斥。问题转换为求从起点 出发,到达集合 中某一点的期望时间。
因为有环,考虑直接爆上高斯消元,时间复杂度 。
看上去不太能过?但是这份代码卡常卡得比较优美,加上又没有出菊花图卡,因此跑得飞快,水过去了。
代码:
#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;
}
现在考虑优化。首先,以题目给出的 为根。然后,考虑令 表示从 出发到 的期望时间。
假设我们现在已经求出了正确的 值,那么,这些 值有何性质呢?
不妨假设 ,其中 指 的父亲。显然,这一性质在 且是叶子时成立,因为此时 。
然后考虑归纳证明。假设对于节点 ,其所有儿子均满足这一性质。
首先,考虑列出传统的转移式 。
接着,因为一切 均满足性质,所以其可被表示成
稍微倒腾一下,得到
这时,如果我们设 ,就得到
于是我们可以愉快地令 ,这样便完成了由儿子的 推出父亲的 的过程。
而明显,对于 ,,因而这部分可以特判掉;对于叶子的 ,上式直接给出了 ,可以一样地计算。
我们要求的就是 。直接dfs一遍完成。
时间复杂度 ,因为还要求逆元。
这类算法被称作树上高斯消元。
代码:
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?