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
Hint
更正:题目有重边有自环也算环(无视掉题目中第一行的定义,即只要求每条边在至多一个简单环中)
Solution
-
设编号是 0 0 0 的点为白点、编号是 1 1 1 的点为黑点。
-
首先要看透这道题的本质:答案等于 点数-边数+环数 (黑白点分开计算)!!
-
具体怎么算一会再说,现在先解决问题的第一步,如何判断读入的边是否能够加入。
-
用 LCT 就很好判断了(也能用树链剖分),若没连通直接加(把边化成点加入);
-
若已连通且路径上的边有的已经被标记过了,就不能加了;
-
否则可以加,并将路径上的边都标记一遍(这个暴力标记,每条边最多被标记一次)。
-
这样的话环的大小我们都能直接算出来。
-
接着我们就能算答案了。
-
先算点数贡献: 一个点是白点的概率其实就是 ( n − 1 n ) t (\frac{n-1}{n})^t (nn−1)t(很关键),所有白点贡献即为 n ∗ ( n − 1 n ) t n*(\frac{n-1}{n})^t n∗(nn−1)t 。
-
黑点呢就是 1 − 1- 1−白点概率,所有黑点: n ∗ ( 1 − ( n − 1 n ) t ) n*(1-(\frac{n-1}{n})^t) n∗(1−(nn−1)t) ,同理。
-
再算边数贡献: 一条边是白边的概率其实就是 ( n − 2 n ) t (\frac{n-2}{n})^t (nn−2)t ,而一条黑边的概率的计算可以“正难则反”,相当于是 ( 1 − x (1-x (1−x是白点 ) ∗ ( 1 − y )*(1-y )∗(1−y是白点 ) ) ) ,即为: 1 − 2 ∗ ( n − 1 n ) t + ( n − 2 n ) t 1-2*(\frac{n-1}{n})^t+(\frac{n-2}{n})^t 1−2∗(nn−1)t+(nn−2)t
-
最后算环数贡献: 一个大小为 m m m 白环的概率比较好算,就是 ( n − m n ) t (\frac{n-m}{n})^t (nn−m)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]=(nn−i)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]=1−j=0∑i−1(f[j]∗g[i−j]∗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=0∑m(−1)i∗Cmi∗(nn−i)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;
}