JZOJ 5987 仙人掌毒题 【树剖+期望的线性性】

题面:

在这里插入图片描述

题目分析:

考场上直接跳了,结果发现。。。

在树中:连通块个数 = 点 - 边
仙人掌满足:如果u到v连接了一棵非树边,不妨称u到v路径上的树边被这条非树边所”覆盖”. 加边的时候保证任意一条树边至多被一条非树边所覆盖,就能确保是一棵仙人掌。上面这个可以用来维护仙人掌
仙人掌中:连通块个数 = 点 - 边 + 环

由期望的线性性,有 E(连通块个数) = E(点) - E(边) + E(环)
所以编号为0的点和编号为1的点分开求期望即可。

一个点为0的期望是 ( n − 1 n ) t (\frac {n-1}n)^t (nn1)t,为1的期望就是 1 − ( n − 1 n ) t 1-(\frac {n-1}n)^t 1(nn1)t
一条边的两个端点都是0的期望就是 ( n − 2 n ) t (\frac {n-2}n)^t (nn2)t
k个点都是0的期望很好求,就是 ( n − k n ) t (\frac {n-k}n)^t (nnk)t

然后想一想k个点都是1的期望怎么求。
考虑容斥至少有i个点为0,有
f [ k ] = ∑ i = 0 k ( − 1 ) i ∗ C k i ∗ ( n − i n ) t f[k]=\sum_{i=0}^k(-1)^i*C_k^i*(\frac {n-i}n)^t f[k]=i=0k(1)iCki(nni)t
环的总大小是O(n)的,所以对每个环暴力容斥算f就可以了。

Code:

#include<cstdio>
#include<cctype>
#include<vector>
#include<algorithm>
#define LL long long
#define maxn 100005
#define maxm 250005
using namespace std;
char cb[1<<15],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<15,stdin),cs==ct)?0:*cs++)
inline void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
const int mod = 998244353;
int n,m,t,w,F[maxn];
int find(int x){return x==F[x]?x:F[x]=find(F[x]);}
namespace Tree{
	int fa[maxn],top[maxn],siz[maxn],son[maxn],dfn[maxn],tim,dep[maxn];
	int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
	inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
	void dfs1(int u){
		siz[u]=1;
		for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=fa[u]){
			fa[v]=u,dep[v]=dep[u]+1;
			dfs1(v);siz[u]+=siz[v];
			if(siz[v]>siz[son[u]]) son[u]=v;
		}
	}
	void dfs2(int u,int tp){
		top[u]=tp,dfn[u]=++tim;
		if(son[u]) dfs2(son[u],tp);
		for(int i=fir[u];i;i=nxt[i]) if(!dfn[to[i]]) dfs2(to[i],to[i]);
	}
	int arr[2][maxn];
	void update(int x,int d){for(int i=x;i<=n;i+=i&-i) arr[0][i]+=d,arr[1][i]+=d*x;}
	int getsum(int x){int s[2]={0};for(int i=x;i;i-=i&-i) s[0]+=arr[0][i],s[1]+=arr[1][i];return s[0]*(x+1)-s[1];}
	int Cover(int u,int v){
		while(top[u]!=top[v]){
			if(dep[top[u]]<dep[top[v]]) swap(u,v);
			update(dfn[top[u]],1),update(dfn[u]+1,-1);
			u=fa[top[u]];
		}
		if(u==v) return u;
		if(dep[u]<dep[v]) swap(u,v);
		update(dfn[v]+1,1),update(dfn[u]+1,-1);
		return v;
	}
	bool Query(int u,int v){
		while(top[u]!=top[v]){
			if(dep[top[u]]<dep[top[v]]) swap(u,v);
			if(getsum(dfn[u])-getsum(dfn[top[u]]-1)) return 1;
			u=fa[top[u]];
		}
		if(u==v) return 0;
		if(dep[u]<dep[v]) swap(u,v);
		return getsum(dfn[u])-getsum(dfn[v]);
	}
}
int Edge,u[maxm],v[maxm],P[maxn],mul[maxn];
LL fac[maxn],inv[maxn],Invn,ans;
bool vis[maxm];
inline int ksm(int a,int b){
	int s=1;
	for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) s=1ll*s*a%mod;
	return s;
}
void FAC_INV(){
	fac[0]=fac[1]=inv[0]=inv[1]=P[0]=1;
	Invn=ksm(ksm(n,t),mod-2);
	for(int i=2;i<=n;i++) fac[i]=fac[i-1]*i%mod,inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<=n;i++) inv[i]=inv[i]*inv[i-1]%mod;
	for(int i=1;i<=n;i++) mul[i]=ksm(i,t),P[n-i]=1ll*mul[i]*Invn%mod;
}
int C(int n,int m){return fac[n]*inv[m]%mod*inv[n-m]%mod;}
inline int solve(int i){
	int ret=0;
	for(int j=0,k=1;j<=i;j++,k=-k) ret=(ret+1ll*k*C(i,j)*mul[n-j])%mod;
	return 1ll*ret*Invn%mod;
}
int main()
{
	//freopen("H.in","r",stdin);
	//freopen("my.out","w",stdout);
	read(n),read(m),read(t),read(w);
	for(int i=1;i<=n;i++) F[i]=i;
	for(int i=1;i<=m;i++){
		read(u[i]),read(v[i]);
		if(find(u[i])!=find(v[i])){
			vis[i]=1;
			Tree::line(u[i],v[i]),Tree::line(v[i],u[i]);
			F[find(u[i])]=find(v[i]);
		}
	}
	for(int i=1;i<=n;i++) if(!Tree::dfn[i]) Tree::dfs1(i),Tree::dfs2(i,i);
	FAC_INV();
	ans=1ll*(P[1]+(w?solve(1):0))*n%mod;
	Edge=solve(2);
	for(int i=1;i<=m;i++){
		if(!vis[i]){
			if(!Tree::Query(u[i],v[i])){
				ans=(ans-P[2]-(w?Edge:0))%mod;
				int pt=Tree::dep[u[i]]+Tree::dep[v[i]]-2*Tree::dep[Tree::Cover(u[i],v[i])]+1;
				ans=(ans+P[pt]+(w?solve(pt):0))%mod;
			}
		}
		else ans=(ans-P[2]-(w?Edge:0))%mod;
		printf("%lld\n",(ans+mod)%mod);
	}
}

PS: 一开始用一个树状数组差分还区间查询。。陷入思维怪圈调不出来,结果发现一个树状数组差分是用单点查询的。。区间查询要两个树状数组。。再次自闭

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值