• 「PKUWC2018」随机游走


    题目

    我暴力过啦

    看到这样的东西我们先搬出来(min-max)容斥

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

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

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

    [max(S)=sum_{Tsubseteq S}(-1)^{|T|}min(T) ]

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

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

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

    显然如果有(xin 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
    ",dp[S]);
    	}
    	return 0;
    }
    

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

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

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

    我们写出(f_x)的转移

    [f_x=frac{f_t+sum_{x ightarrow c}f_c}{d_x}+1 ]

    其中(c)(x)的儿子

    也就是

    [d_xf_x=f_t+sum_{x ightarrow c}f_c+d_x ]

    [d_xf_x=f_t+sum_{x ightarrow c}A_cf_x+sum_{x ightarrow c}B_c+d_x ]

    [(d_x-sum_{x ightarrow c}A_c)f_x=f_t+sum_{x ightarrow c}B_c+d_x ]

    于是我们现在解得

    [A_x=frac{1}{d_x-sum_{x ightarrow c}A_c} ]

    [B_x=frac{sum_{x ightarrow c}B_c+d_x}{d_x-sum_{x ightarrow c}A_c} ]

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

    代码就不写了

  • 相关阅读:
    读书笔记_Effective_C++_条款十七:以独立语句将new产生的对象置入智能指针
    读书笔记_Effective_C++_条款二十二:将成员变量声明为private
    读书笔记_Effective_C++_条款二十:宁以passbyreferencetoconst替换passbyvalue
    读书笔记_Effective_C++_条款十五:在资源类管理类中提供对原始资源的访问
    读书笔记_Effective_C++_条款二十一:当必须返回对象时,别妄想返回其reference
    读书笔记_Effective_C++_条款十六:成对使用new和delete时要采取相同的形式
    读书笔记_Effective_C++_条款十四:在资源管理类中小心copying行为
    读书笔记_Effective_C++_条款十八:让接口容易被正确使用,不易被误用
    c#设置开机自动启动程序本篇文章来源于:
    发现21cn邮箱存在严重的安全漏洞及风险,对于申请密保的邮箱可以随便更改任意用户的密码
  • 原文地址:https://www.cnblogs.com/asuldb/p/10776502.html
Copyright © 2020-2023  润新知