Processing math: 2%

「PKUWC2018」随机游走

题目

我暴力过啦

看到这样的东西我们先搬出来minmax容斥

我们设max(S)表示x到达点集S的期望最晚时间,也就是我们要求的答案了

显然我们也很难求出这个东西,但是我们有min-max容斥

min(S)表示x第一次到达S的期望时间,我们就有

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

我们现在只需要求出所有min(S)之后用fwt做一个子集和就好了

尽管这是一棵树,但是我并没有推出什么优美的转移方程,我们考虑暴力高消

dp_{x,s}表示从x到集合s的期望步数

显然如果有x\in s,那么dp_{x,s}=0

否则

dp_{x,s}=1+\sum_{(x,v)\in e}\frac{dp_{v,s}}{d_x}

于是我们对于每一种s分别列方程转移就好了

复杂度是O(2^{n}n^3)

但是我们注意到没有包含x点的集合只有2^{n-1}个,同时高消的常数小至\frac{1}{8},同时很多高消都没有跑满,于是还是跑的挺快的

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long
inline int read() {
	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
const int mod=998244353;
struct E{int v,nxt;}e[40];
int num,n,Q,X,len;
int dp[262145];
int head[19],d[19];
int cnt[262145];
int a[20][20],id[20],vis[20],to[20],inv[20],ans[20];
inline int ksm(int a,int b) {
	int S=1;
	while(b) {if(b&1) S=1ll*S*a%mod;b>>=1;a=1ll*a*a%mod;}
	return S;
}
inline void add(int x,int y) {
	e[++num].v=y;e[num].nxt=head[x];head[x]=num;
}
inline void solve(int S) {
	int t=0;memset(vis,0,sizeof(vis));
	for(re int i=1;i<=n;i++) {
		if(S&(1<<(i-1))) continue;
		vis[i]=1;id[++t]=i;to[i]=t;
	}
	if(!vis[X]) return;
	memset(a,0,sizeof(a));
	for(re int i=1;i<=t;i++) {
		int x=id[i];
		for(re int j=head[x];j;j=e[j].nxt)
		if(vis[e[j].v]) a[i][to[e[j].v]]=inv[d[x]];
		a[i][i]=mod-1;a[i][t+1]=mod-1;
	}
	for(re int i=1;i<=t;i++) {
		int p=i;
		for(p=i;p<=t;p++) if(a[p][i]) break;
		if(p!=i) std::swap(a[i],a[p]);
		int now=ksm(a[i][i],mod-2);
		for(re int j=n+1;j>=i;--j) a[i][j]=1ll*a[i][j]*now%mod;
		for(re int j=i+1;j<=t;j++)
			for(re int k=t+1;k>=i;--k) {
				a[j][k]=(a[j][k]-1ll*a[j][i]*a[i][k]%mod);
				if(a[j][k]<0) a[j][k]=(a[j][k]+mod)%mod;
			}
	}
	ans[t]=a[t][t+1];
	for(re int i=t-1;i>=0;--i) {
		ans[i]=a[i][t+1];
		for(re int j=i+1;j<=t;j++) {
			ans[i]-=1ll*a[i][j]*ans[j]%mod;
			if(ans[i]<0) ans[i]=(ans[i]+mod)%mod;
		}
	}
	dp[S]=ans[to[X]];
	if(cnt[S]&1) return;
	dp[S]=mod-dp[S];
}
inline void Fwt(int *f) {
	for(re int i=2;i<=len;i<<=1)
		for(re int ln=i>>1,l=0;l<len;l+=i)
			for(re int x=l;x<l+ln;++x) {
				f[x+ln]+=f[x];
				if(f[x+ln]>=mod) f[x+ln]%=mod;
			}
}
int main() {
	n=read(),Q=read();X=read();
	inv[1]=1;
	for(re int i=2;i<=n;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(re int x,y,i=1;i<n;i++) {
		x=read(),y=read(),d[x]++,d[y]++;
		add(x,y),add(y,x);
	}
	len=(1<<n);
	for(re int i=1;i<len;i++) cnt[i]=cnt[i>>1]+(i&1);
	for(re int i=1;i<len;i++) solve(i);
	Fwt(dp);
	while(Q--) {
		int k=read(),S=0;
		for(re int i=1;i<=k;i++) S|=(1<<(read()-1));
		printf("%d\n",dp[S]);
	}
	return 0;
}

我还是来补一下正解吧,据说这是树上随机游走的套路

我们设f_x表示从x到点集s的期望步数

据说树上路径唯一我们可以设f_x=A_xf_x+B_x

我们写出f_x的转移

f_x=\frac{f_t+\sum_{x\rightarrow c}f_c}{d_x}+1

其中cx的儿子

也就是

d_xf_x=f_t+\sum_{x\rightarrow c}f_c+d_x

d_xf_x=f_t+\sum_{x\rightarrow c}A_cf_x+\sum_{x\rightarrow c}B_c+d_x

(d_x-\sum_{x\rightarrow c}A_c)f_x=f_t+\sum_{x\rightarrow c}B_c+d_x

于是我们现在解得

A_x=\frac{1}{d_x-\sum_{x\rightarrow c}A_c}

B_x=\frac{\sum_{x\rightarrow c}B_c+d_x}{d_x-\sum_{x\rightarrow c}A_c}

对于在S集合的点显然满足A=B=0,叶子结点的A,B我们能直接算,我们一路推到根由于根没有父亲,所以f_{rt}=B_{rt},这样我们就能把所有的f_x都算出来了,复杂度是O(2^nn)

代码就不写了

posted @   asuldb  阅读(162)  评论(0编辑  收藏  举报
编辑推荐:
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 对象命名为何需要避免'-er'和'-or'后缀
阅读排行:
· “你见过凌晨四点的洛杉矶吗?”--《我们为什么要睡觉》
· 编程神器Trae:当我用上后,才知道自己的创造力被低估了多少
· C# 从零开始使用Layui.Wpf库开发WPF客户端
· C#/.NET/.NET Core技术前沿周刊 | 第 31 期(2025年3.17-3.23)
· 开发的设计和重构,为开发效率服务
点击右上角即可分享
微信分享提示