动物园里有三种动物,大象、狗爷和老鼠。大象能战胜狗爷,狗爷能战胜老鼠,老鼠能战胜大象。
动物园里 n 有个笼子,编号从 1 到 n,每一个笼子里都关了一个小动物,因为 Smart视力不好看不清笼子里的动物是大象还是老鼠,所以他假装每个笼子里三种动物以相同的概率出现,即一共有 3n 种可能的初始状态。
动物园的表演开始了,在表演过程中发生了m 个事件:
1 u v,管理员把第 v 个笼子拆掉,并把笼子里的动物关到了第 u 个笼子里。两个动物在同一个笼子里会打架,胜者会活下来。如果种类相同则原来在 u 中的动物会活下来(主场优势)
2 u,表示 Smart 向 Sarah 提了个问题,有多少种初始状态使得原来在第 u 个笼子里的动物现在还活着。
Sarah 觉得这道问题太水了不屑于做,于是把它交给了你。
Input
第一行输入两个整数 n,m,表示笼子的个数和事件个数。
接下来 m 行,每行描述了一个事件。输入保证在所有第一类事件中,笼子 u,v 在对应的事件发生时都还没有被拆掉。
Output
对于每一个第二类事件,输出一行一个整数,表示满足条件的初始状态数。答案可能很大,请对 998244353 取模后输出。
Sample Input
3 5
2 1
1 2 1
2 1
1 2 3
2 1
Sample Output
27
9
6
这是一道带权并查集并查集好题,我们考虑把v笼子里的动物移动到u里,枚举所有情况可知有 的概率u里的动物获胜,有
的概率v里的动物获胜。那么和并查集有什么关系呢?我们维护一个每个动物存活的概率,合并的时候用并查集。每次合并u,v的时候u的概率乘
,v的概率乘
,找父节点时进行压缩路径和更新权值,由于最后要模一个质数,我们用费马小定理加快速幂。
代码:
#include<bits/stdc++.h>
using namespace std;
const long long MOD=998244353;
long long f[1000005],w[500005],p;
long long ksm(long long a,long long b){
long long res=1;
while(b>0){
if(b&1){
res=(res*a)%MOD;
}
a=a*a%MOD;
b=b>>1;
}
return res%MOD;
}
long long find(long long x){
if(x==f[x]){
return x;
}
long long t=find(f[x]);
w[x]=(w[x]*w[f[x]])%MOD;
return f[x]=t;
}
int main(){
long long n,m;
cin>>n>>m;
long long p=1,inv;
for(long long i=1;i<=n;i++){
p=(3ll*p)%MOD;
}
inv=ksm(3,MOD-2)%MOD;
for(long long i=1;i<=2*n;i++){
f[i]=i,w[i]=1;
}
for(long long i=1;i<=m;i++){
long long op;
scanf("%lld",&op);
if(op==1){
long long x,y;
scanf("%lld%lld",&x,&y);
long long fx=find(x),fy=find(y);
w[fx]=2*inv;
w[fy]=inv;
f[fx]=f[fy]=++n;
}
else{
long long x;
scanf("%lld",&x);
find(x);
printf("%lld\n",(1ll*p*w[x])%MOD);
}
}
return 0;
}