最值反演&[PKUWC2018][loj2542]随机游走

9 篇文章 0 订阅
5 篇文章 0 订阅

前言

这是学习容斥过程中的一个比较裸的题了

题意简介

题目链接

题目大意

给出一棵 n n n个点的树,给出树上的一个点 x x x
现在进行 Q Q Q次询问,每次询问一个点集,求从 x x x点开始进行随机随机游走,第一次走遍这个点集的期望步数
(随机游走即每次等概率走到一个与自己相邻的点)

数据范围

1 ≤ n ≤ 18 , 1 ≤ Q ≤ 5000 1\le n\le18,1\le Q\le 5000 1n18,1Q5000

前置知识(最值反演 Min-Max容斥)

m a x { S } = ∑ T ⊆ S ( − 1 ) ∣ T ∣ + 1 m i n { T } max\{S\}=\sum_{T\subseteq S}(-1)^{|T|+1}min\{T\} max{S}=TS(1)T+1min{T}
具体介绍可以看我的博客
介绍链接
这个式子套上期望依然成立

题解

所有点第一次期望访问的步数的集合 S S S m a x { S } max\{S\} max{S}就是每个点都被走过的期望步数, m i n { S } min\{S\} min{S}代表里任意一个点被第一次访问的期望时间
我们发现我们现在要求的就是 m a x { S } max\{S\} max{S}
但是直接求不好求,所以用Min-Max容斥转化成求 m i n { S } min\{S\} min{S}
我们可以预处理 m i n { S } min\{S\} min{S},并且 Θ ( 3 n ) \Theta(3^n) Θ(3n)枚举子集计算答案
那么现在就是考虑如何求 m i n { S } min\{S\} min{S}
这个似乎我当时在考场上就推出来了
考虑对于一个集合 S S S,设 f ( x ) f(x) f(x)为从 x x x点开始随机游第一次访问集合 S S S的期望步数
然后分类讨论
x ∈ S x\in S xS
f ( x ) = 0 f(x)=0 f(x)=0
x ∉ S x\notin S x/S
f ( x ) = 1 + 1 d x ∑ e d g e ( x , y ) f ( y ) f(x)=1+\frac{1}{d_x}\sum_{edge(x,y)}f(y) f(x)=1+dx1edge(x,y)f(y)
其中 d x d_x dx x x x的度数, e d g e ( x , y ) edge(x,y) edge(x,y)代表 x , y x,y x,y间有边
对于 x ∉ S x\notin S x/S的部分随便定一个根 r o o t ∈ S root\in S rootS
我们发现对于 x x x是叶子节点的情况: f ( x ) = f ( f a x ) + 1 f(x)=f(fa_x)+1 f(x)=f(fax)+1
我们发现对于 x x x是非子节点的情况: f ( x ) = 1 + 1 d x ∑ y = s o n x f ( y ) + 1 d x f ( f a x ) f(x)=1+\frac{1}{d_x}\sum_{y=son_x}f(y)+\frac{1}{d_x}f(fa_x) f(x)=1+dx1y=sonxf(y)+dx1f(fax)
我们设一个节点 x x x的答案为 ( a , b ) (a,b) (a,b)代表: f ( x ) = a + b f ( f a x ) f(x)=a+bf(fa_x) f(x)=a+bf(fax)
我们发现,对于任何一个节点的答案 ( a , b ) (a,b) (a,b),一定满足 b ≤ 1 b\le 1 b1(实数意义下)
(由于 r o o t ∈ S root\in S rootS,所以不会有枚举的节点没有 f a fa fa的情况)
所以对于非叶子节点的 x x x,等号右侧的 f ( x ) f(x) f(x)的系数一定小于 1 1 1,一项即可算出当前的的答案 ( a , b ) (a,b) (a,b)
算完答案 ( a , b ) (a,b) (a,b)后,即可从根倒着推回去
非常方便
这样算出来复杂度是 Θ ( 3 n ) \Theta(3^n) Θ(3n)的,需要卡常,我写了一波被卡成70
这个时候就需要优化瓶颈
发现是裸的FMT,写一发就好了
复杂度 Θ ( 2 n n l o g n ) \Theta(2^nnlogn) Θ(2nnlogn)

代码

#include<cstdio>
#include<cctype>
namespace fast_IO
{
    const int IN_LEN=10000000,OUT_LEN=10000000;
    char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
    inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
    inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
    inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define getchar() getchar_()
#define putchar(x) putchar_((x))
//#include<ctime>
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
//template <typename T> inline void swap(T*a,T*b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
    char cu=getchar();x=0;bool fla=0;
    while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
    while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
    if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
    if(x>=10)printe(x/10);
    putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
    if(x<0)putchar('-'),printe(-x);
    else printe(x);
}
const LL mod=998244353;
inline LL pow(LL x,LL y)
{
	LL res=1;
	for(;y;y>>=1,x=x*x%mod)if(y&1)res=res*x%mod;
	return res; 
}
int n,q,x,k;
int bit[20],inv[20],d[20];
int head[20],nxt[40],tow[40],tmp;
inline void addb(const int u,const int v)
{
	tmp++;
	nxt[tmp]=head[u];
	head[u]=tmp;
	tow[tmp]=v;
}
int zt;
struct Ans
{
	int a,b;
	inline Ans operator +(const Ans&y)const{return (Ans){a+y.a,b+y.b};}
}Q[20];
LL ans[20];
void dfs1(const int u,const int fa)
{
	if(zt&bit[u])
	{
		for(rg int i=head[u];i;i=nxt[i])
		{
			const int v=tow[i];
			if(v==fa)continue;
			dfs1(v,u);
		}
		Q[u].a=Q[u].b=0;
	}
	else
	{
		const LL INV=inv[d[u]];
		Q[u]=(Ans){1,INV};LL xs=1;
		for(rg int i=head[u];i;i=nxt[i])
		{
			const int v=tow[i];
			if(v==fa)continue;
			dfs1(v,u);
			xs=(xs+mod-INV*Q[v].b%mod)%mod;
			Q[u].a=(Q[u].a+Q[v].a*INV)%mod;
		}
		const LL INTO=pow(xs,mod-2);
		Q[u].a=INTO*Q[u].a%mod;
		Q[u].b=INTO*Q[u].b%mod;
	}
}
void dfs2(const int u,const int fa)
{
	if(zt&bit[u])
	{
		ans[u]=0;
		for(rg int i=head[u];i;i=nxt[i])
		{
			const int v=tow[i];
			if(v==fa)continue;
			dfs2(v,u);
		}
	}
	else
	{
		ans[u]=(Q[u].a+ans[fa]*Q[u].b)%mod;
		for(rg int i=head[u];i;i=nxt[i])
		{
			const int v=tow[i];
			if(v==fa)continue;
			dfs2(v,u);
		}
	}
}
int res[524288];
int pc[524288];
int main()
{
	bit[1]=1;
	for(rg int i=2;i<=19;i++)bit[i]=bit[i-1]<<1;
	inv[1]=1;for(rg int i=2;i<=19;i++)inv[i]=(LL)(mod-mod/i)*inv[mod%i]%mod;
	read(n),read(q),read(x);
	for(rg int i=1;i<n;i++)
	{
		int u,v;read(u),read(v);
		addb(u,v),addb(v,u);
		d[u]++,d[v]++;
	}
	for(zt=1;zt<bit[n+1];zt++)
	{
		pc[zt]=pc[zt>>1]^(zt&1);
		for(rg int i=1;i<=n;i++)
			if(zt&bit[i])
			{
				dfs1(i,i);
				dfs2(i,i);
				break;
			}
		if(pc[zt]&1)res[zt]=ans[x];
		else res[zt]=(mod-ans[x])%mod;
	}
	for(rg int i=1;i<bit[n+1];i<<=1)
    	for(rg int j=0;j<bit[n+1];j++)
			if(i&j)
				res[j]=(res[j]+res[j^i])%mod;
	while(q--)
	{
		read(k),zt=0;
		while(k--)read(x),zt|=bit[x];
		print(res[zt]),putchar('\n');
	}
	return flush(),0;
}

不知为何分类讨论使得程序拉的特别长

总结

min-max容斥很重要,使得整题好做了很多
然后这算是FMT的应用吧,清真好题

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值