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

12 篇文章 0 订阅
11 篇文章 0 订阅

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

  • 设编号是 0 0 0 的点为白点、编号是 1 1 1 的点为黑点。

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

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

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

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

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

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

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

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

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

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

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

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

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

  • f [ i ] f[i] f[i] 表示大小为 i i i 的环在 t t t 次操作后全黑的概率,再设一个辅助数组 g [ i ] g[i] g[i] 表示包含 i i i 个点的集合在 t t t 次操作后全白的概率。根据前面的讨论即有: g [ i ] = ( n − i n ) t g[i]=(\frac{n-i}{n})^t g[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 = 0 i − 1 ( f [ j ] ∗ g [ i − j ] ∗ C i j ) 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)

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

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

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

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

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

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

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

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

  • 总复杂度即为 O ( n   l o g   t + m   l o g   n ) O(n\ log\ t+m\ log\ n) O(n log t+m log n) ,其中 m   l o g   n m\ log\ n m 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值