• 【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}cdotfrac1{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('
    ');}
    		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;
    }
    
  • 相关阅读:
    方法的调用机制
    类的成员之二:方法
    类的成员之一:属性
    关键字static
    构造器
    this关键字
    递归方法(recursion)
    方法重载(二)
    GTID 跳过脚本
    mydumper 找不到libmysqlclient.so.20
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/CometOJDay3Div1I.html
Copyright © 2020-2023  润新知