JZOJ 5987. 【WC2019模拟2019.1.4】仙人掌毒题

本文深入探讨了Cactus图算法的实现细节,包括输入输出格式、样例输入输出、数据约束提示以及解决方案。通过理解算法本质,即答案等于点数-边数+环数,文章详细介绍了使用LCT进行边的判断、点数与边数的贡献计算,以及环数贡献的两种计算方法:分治NTT和容斥原理。同时,提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

Description
Description

Input

输入文件cactus .in
第一行4个空格隔开的整数n,m,t,w
接下来m行,每行两个空格隔开的整数u,v,表示m次加边操作.

Output

输出文件为cactus.out
输出m行,每行一个整数,表示期望模998244353的结果.

Sample Input

输入1:

5 5 1 1
1 2
1 3
2 3
3 4
1 5

输入2:

5 5 0 1
1 2
1 3
2 3
3 4
1 5

输入3:

5 5 3 1
1 2
1 3
2 3
3 4
1 5

Sample Output

输出1:

199648875
399297745
798595486
3
199648873

输出2:

4
3
3
2
1

输出3:

934356719
870469080
902412899
838525260
774637621

Data Constraint

Data Constraint

Hint

更正:题目有重边有自环也算环(无视掉题目中第一行的定义,即只要求每条边在至多一个简单环中)

Solution

  • 设编号是 000 的点为白点、编号是 111 的点为黑点。

  • 首先要看透这道题的本质:答案等于 点数-边数+环数 (黑白点分开计算)!!

  • 具体怎么算一会再说,现在先解决问题的第一步,如何判断读入的边是否能够加入。

  • 用 LCT 就很好判断了(也能用树链剖分),若没连通直接加(把边化成点加入);

  • 若已连通且路径上的边有的已经被标记过了,就不能加了;

  • 否则可以加,并将路径上的边都标记一遍(这个暴力标记,每条边最多被标记一次)。

  • 这样的话环的大小我们都能直接算出来。

  • 接着我们就能算答案了。

  • 先算点数贡献: 一个点是白点的概率其实就是 (n−1n)t(\frac{n-1}{n})^t(nn1)t(很关键),所有白点贡献即为 n∗(n−1n)tn*(\frac{n-1}{n})^tn(nn1)t

  • 黑点呢就是 1−1-1白点概率,所有黑点:n∗(1−(n−1n)t)n*(1-(\frac{n-1}{n})^t)n(1(nn1)t) ,同理。

  • 再算边数贡献: 一条边是白边的概率其实就是 (n−2n)t(\frac{n-2}{n})^t(nn2)t ,而一条黑边的概率的计算可以“正难则反”,相当于是 (1−x(1-x(1x是白点)∗(1−y)*(1-y)(1y是白点))) ,即为:1−2∗(n−1n)t+(n−2n)t1-2*(\frac{n-1}{n})^t+(\frac{n-2}{n})^t12(nn1)t+(nn2)t

  • 最后算环数贡献: 一个大小为 mmm 白环的概率比较好算,就是 (n−mn)t(\frac{n-m}{n})^t(nnm)t ,本质跟前面白点、白边是一样的。

  • 但是一个大小为 mmm 的黑环的概率就不太好算了。下面给出两种方法:

  • 方法①:分治NTT。 这样会码量大而且跑得慢,不过简单直接,本质是DP计算。

  • f[i]f[i]f[i] 表示大小为 iii 的环在 ttt 次操作后全黑的概率,再设一个辅助数组 g[i]g[i]g[i] 表示包含 iii 个点的集合在 ttt 次操作后全白的概率。根据前面的讨论即有:g[i]=(n−in)tg[i]=(\frac{n-i}{n})^tg[i]=(nni)t

  • O(n)O(n)O(n) 预处理出 g[i]g[i]g[i] ,再用补集转化的思想可以求出 f[i]f[i]f[i]

  • 1 减去所有不是全黑的情况的概率就是全黑的概率 ,即:f[i]=1−∑j=0i−1(f[j]∗g[i−j]∗Cij)f[i]=1-\sum_{j=0}^{i-1}(f[j]*g[i-j]*C_i^j)f[i]=1j=0i1(f[j]g[ij]Cij)

  • 这里的 jjj 枚举的是环中全黑的点的个数。因为我们需要在这 iii 个点中选出 jjj 个使其全黑,所以方案数要乘一个组合数 CijC_i^jCij

  • 这个用分治NTT就可以 O(n log2n)O(n\ log^2n)O(n log2n) 求出 f[i]f[i]f[i] 了(分治时先做完左边,再算值加到右边)。

  • 方法②:容斥。 这样计算很快、码量小,但没那么好想。下面的代码我打的是容斥做法。

  • 我们发现不需要将 111nnn 每个 f[i]f[i]f[i] 都算出来,只用对特定的环计算即可。

  • 于是可得容斥:f[m]=∑i=0m(−1)i∗Cmi∗(n−in)tf[m]=\sum_{i=0}^{m}(-1)^i*C_m^i*(\frac{n-i}{n})^tf[m]=i=0m(1)iCmi(nni)t

  • 上式中 iii 枚举的是 该环中至少有 iii 个白点 ,正确性显然。

  • 于是我们预处理阶乘、逆元,每个环就可以 O(m log t)O(m\ log\ t)O(m log t) 计算其全黑概率了。

  • 由于每个环只用算一次,所以这部分复杂度就是 ∑m log t=n log t\sum m\ log\ t=n\ log\ tm log t=n log t

  • 总复杂度即为 O(n log t+m log n)O(n\ log\ t+m\ log\ n)O(n log t+m log n) ,其中 m log nm\ log\ nm log n 是用 LCT 判加边的复杂度。

Code

#include<cstdio>
#include<algorithm>
#include<cctype>
using namespace std;
typedef long long LL;
const int N=1e5+5,M=N*3,mo=998244353;
int tot,top,ans;
int fa[M],s[M][2],sum[M],key[M],st[M];
bool rev[M],vis[M],bz[M];
int f[N],g[N],inv[N],fs[N];
inline int read()
{
	int X=0,w=0; char ch=0;
	while(!isdigit(ch)) w|=ch=='-',ch=getchar();
	while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
	return w?-X:X;
}
void write(int x)
{
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
inline bool pd(int x)
{
	return s[fa[x]][1]==x;
}
inline bool isroot(int x)
{
	return s[fa[x]][0]^x && s[fa[x]][1]^x;
}
inline void reverse(int x)
{
	if(x) swap(s[x][0],s[x][1]),rev[x]^=1;
}
inline void update(int x)
{
	sum[x]=sum[s[x][0]]+sum[s[x][1]]+key[x];
	vis[x]=vis[s[x][0]]|vis[s[x][1]]|bz[x];
}
inline void down(int x)
{
	if(rev[x])
	{
		reverse(s[x][0]),reverse(s[x][1]);
		rev[x]=false;
	}
}
inline void rotate(int x)
{
	int y=fa[x],w=pd(x);
	if((fa[x]=fa[y]) && !isroot(y)) s[fa[y]][pd(y)]=x;
	if(s[y][w]=s[x][w^1]) fa[s[y][w]]=y;
	s[fa[y]=x][w^1]=y;
	update(y);
}
inline void splay(int x)
{
	for(int y=st[top=1]=x;!isroot(y);y=fa[y]) st[++top]=fa[y];
	while(top) down(st[top--]);
	for(int y;!isroot(x);rotate(x))
		if(!isroot(y=fa[x])) rotate(pd(x)==pd(y)?y:x);
	update(x);
}
inline void access(int x)
{
	for(int y=0;x;x=fa[y=x])
	{
		splay(x);
		s[x][1]=y;
		update(x);
	}
}
inline void mkroot(int x)
{
	access(x),splay(x),reverse(x);
}
inline void link(int x,int y)
{
	mkroot(x),fa[x]=y;
}
void dfs(int x)
{
	if(s[x][0]) dfs(s[x][0]);
	if(s[x][1]) dfs(s[x][1]);
	if(x>tot) bz[x]=true;
	update(x);
}
inline int ksm(int x,int y)
{
	int s=1;
	while(y)
	{
		if(y&1) s=(LL)s*x%mo;
		x=(LL)x*x%mo;
		y>>=1;
	}
	return s;
}
inline int C(int x,int y)
{
	return (LL)f[x]*g[y]%mo*g[x-y]%mo;
}
int get(int x)
{
	return fs[x]==x?x:fs[x]=get(fs[x]);
}
int main()
{
	freopen("cactus.in","r",stdin);
	freopen("cactus.out","w",stdout);
	int n=read(),m=read(),t=read(),w=read();
	tot=n;
	f[0]=g[0]=1;
	for(int i=1;i<=n;i++) f[i]=(LL)f[i-1]*i%mo;
	g[n]=ksm(f[n],mo-2);
	for(int i=n-1;i;i--) g[i]=(LL)g[i+1]*(i+1)%mo;
	inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=(LL)(mo-mo/i)*inv[mo%i]%mo;
	for(int i=1;i<=n;i++) fs[i]=i;
	int wnode=ksm((LL)(n-1)*inv[n]%mo,t);
	int wedge=ksm((LL)(n-2)*inv[n]%mo,t);
	int bedge=(1-(LL)2*wnode%mo+mo+wedge)%mo;
	ans=(LL)wnode*n%mo;
	if(w) ans=(ans+(LL)(1-wnode+mo)*n)%mo;
	while(m--)
	{
		int x=read(),y=read();
		if(get(x)^get(y))
		{
			fs[get(x)]=get(y);
			key[++n]=1;
			link(x,n);
			link(n,y);
			ans=(ans+mo-wedge)%mo;
			if(w) ans=(ans+mo-bedge)%mo;
			write(ans),putchar('\n');
			continue;
		}
		mkroot(x),access(y),splay(y);
		if(vis[y])
		{
			write(ans),putchar('\n');
			continue;
		}
		ans=(ans-wedge+mo)%mo;
		if(w) ans=(ans-bedge+mo)%mo;
		dfs(y);
		int ring=sum[y]+1;
		ans=(ans+ksm((LL)(tot-ring)*inv[tot]%mo,t))%mo;
		if(w)
			for(int i=0;i<=ring;i++)
			{
				int ss=(LL)C(ring,i)*ksm((LL)(tot-i)*inv[tot]%mo,t)%mo;
				if(i&1) ss=mo-ss;
				ans=(ans+ss)%mo;
			}
		write(ans),putchar('\n');
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值