把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CCPC-Wannafly Winter Camp Day3 (Div1) I】石头剪刀布(按秩合并并查集)

点此看题面

大致题意:\(n\)个人,第\(i\)个人坐在编号为\(i\)的座位上,每个人等概率有石头、剪刀、布中的一张卡片。有两种操作:第一种是第\(y\)个人挑战第\(x\)个人,如果胜利则\(x\)被淘汰,\(y\)坐到\(x\)的座位上,否则\(y\)被淘汰;第二种是询问第\(x\)个人存活的概率。

一个简单的结论

考虑被别人挑战,平或胜都不会被淘汰,因此存活率为\(\frac 23\)

考虑挑战别人,只有胜才不会被淘汰,因此存活率为\(\frac 13\)

因此,如果有一个人被别人挑战了\(A\)次,挑战了别人\(B\)次,那么他的存活率自然就是:

\[(\frac 23)^A*(\frac 13)^B=\frac{2^A}{3^A}\cdot\frac1{3^B}=\frac{2^A}{3^{A+B}} \]

而存活率再乘上总情况数\(3^n\),就是所询问的答案了:

\[3^n*\frac{2^A}{3^{A+B}}=3^{n-A-B}*2^A \]

考虑如何维护答案

设想当一个人挑战另一个人之后,无论是哪一种情况,二人中都必将有且仅有一人被淘汰。

所以接下来的操作需要对他们一同进行修改。

这样一来,就可以想到使用并查集来进行维护了。

但由于有修改操作,所以我们不能简单粗暴的路径压缩(其实也可以通过一个稍微麻烦点的技巧来使用路径压缩做此题),而是要用按秩合并

我们可以对于每个节点维护它对\(A\)\(B\)两个值造成的变化量,然后查询时不停向上跳即可。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define X 998244353
#define swap(x,y) (x^=y^=x^=y)
#define Qinv(x) Qpow(x,X-2)
using namespace std;
int n;
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
		#define tn(x) (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn(x)+(c&15),D);}
		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
		Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
		I void clear() {fwrite(FO,1,C,stdout),C=0;}
}F;
class UnionFindSet//并查集
{
	private:
		static const int SZ=N;int f[SZ+5],g[SZ+5],A[SZ+5],B[SZ+5];
		I int getfa(CI x) {return f[x]^x?getfa(f[x]):x;}//找祖先
	public:
		I void Init(CI n) {for(RI i=1;i<=n;++i) g[f[i]=i]=1;}//初始化
		I void Union(RI x,RI y)//合并A和B
		{
			if(!((x=getfa(x))^(y=getfa(y)))) return;//如果祖先相同,则退出函数(理论上来说不会出现这种情况)
			++A[x],++B[y],g[x]<g[y]&&swap(x,y),//将x被挑战次数的变化量加1,将y挑战次数的变化量加1,将深度较大的节点作为x
			A[y]-=A[f[y]=x],B[y]-=B[x],!(g[x]^g[y])&&++g[x];//将y的两个变化量减去x的两个变化量,然后合并
		}
		I void Operate(CI x,int& a,int& b) {f[x]^x&&(Operate(f[x],a,b),0),a+=A[x],b+=B[x];}//求出x的两个变化量
}U;
I int Qpow(RI x,RI y) {RI res=1;W(y) y&1&&(res=1LL*res*x%X),x=1LL*x*x%X,y>>=1;return res;}//快速幂
int main()
{
	RI Qtot,i,op,x,y,A,B;F.read(n,Qtot),U.Init(n);W(Qtot--)
	{
		if(F.read(op,x),op^1) A=B=0,U.Operate(x,A,B),F.writeln(1LL*Qpow(3,n-A-B)*Qpow(2,A)%X);//对于询问,求解并输出答案
		else F.read(y),U.Union(x,y);//对于一次挑战,合并两个节点
	}
	return F.clear(),0;
}
posted @ 2019-03-06 16:13  TheLostWeak  阅读(202)  评论(0编辑  收藏  举报