2018 CCPC-Wannafly Winter Camp Day3 I.石头剪刀布(带权并查集 按秩合并/路径压缩)

题目

思路来源

https://www.cnblogs.com/mountaink/p/10322513.html 按秩合并

https://www.cnblogs.com/dilthey/p/10433419.html 路径压缩

题解

首先,考虑答案怎么算,设被询问的人最终出的是石头

则他作为a次主场赢的人,出的是石头或者剪刀;他作为b次,客场赢的人, 出的是剪刀,

答案是2^{a}*1^{b}*3^{n-1-a-b},其中被询问的人放石头,其余按主、客、空位计算

三种情况对称,乘3即可,答案是3^{n}*(\frac{2}{3})^{a}*(\frac{1}{3})^{b}

当然也可以直接理解化简式的含义,随便取*用概率限制主场*用概率限制客场

 

形如1类型的操作y->x,根据这个关系建边,最后是一棵有根树,

由于撤去的座位一定是之前有的座位,故建的新边,一定是上一时刻的树根a和树根b之间的建边,

这样在任意时刻,都是若干棵有根树的森林,

考虑把一棵树视为一个并查集,并查集内维护的是,能坐在树根这个座位的点的集合

 

那么对于合并y-->x来说,就是可能坐下y的一个人向可能坐在x的一个人发起了挑战,

此时,x所在并查集的主场数均+1,y所在的并查集的客场数均+1,

实际操作时,把这个add标记打到根上,

合并的时候,x挂到了y上,此时多加了y的标记量,

x作为根时的标记应不变,故减去多余量,

 

以下参考两种写法,

①按秩合并,是直接的做法,

用logn控制深度,每次查询的时候暴力上跳统计祖先该下放的标记总和

 

②路径压缩,是奇技淫巧,考虑一种情况,

x是根,且x是y的父亲,y是z的父亲,对x加a1,查询z时路径压缩,再对x加a2,再查询z,

此时z的值为a1+a1+a2,但实际应为a1+a2,是x的下标重复下放导致的,

其本质是根无法区分下放的标记和没下放的标记,

 

那我们就专把根留出来用于攒标记不下放,

其余的点由于下放后不会后续再产生新的没下放的标记,可以下放并路径压缩,这样树深也不过为2,

查询时,如果是根,就只考虑标记量;否则考虑根的标记量+当前节点的权值

代码1(按秩合并)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10,mod=998244353;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back
int n,m,op,x,y,par[N],rk[N],a[N],b[N];//a:主场 b:客场
ll i13,i23;
ll modpow(ll x,ll n,ll mod){
    ll res=1;
    for(;n;n/=2,x=x*x%mod){
        if(n&1)res=res*x%mod;
    }
    return res;
}
int find(int x){
    for(;x!=par[x];x=par[x]);
    return x;
}
//y向x发起挑战 y客场 x主场
void unite(int x,int y){
    x=find(x),y=find(y);
    a[x]++;b[y]++;
    if(rk[x]>rk[y])swap(x,y);
    if(rk[x]==rk[y])rk[y]++;
    //x向y上挂 并减去多余的向下传的标记
    par[x]=y;
    a[x]-=a[y];
    b[x]-=b[y];
}
//3^n * (2/3)^a *(1/3)^b
ll cal(ll x){
    ll na=a[x],nb=b[x];
    for(;x!=par[x];x=par[x],na+=a[x],nb+=b[x]);
    //printf("na:%lld nb:%lld\n",na,nb);
    ll res=modpow(3,n,mod);
    res=res*modpow(i23,na,mod)%mod;
    res=res*modpow(i13,nb,mod)%mod;
    return res;
}
int main(){
    i13=modpow(3,mod-2,mod);//1/3
    i23=2ll*i13%mod;//2/3
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        rk[i]=1;
        par[i]=i;
    }
    for(int i=1;i<=m;++i){
        scanf("%d%d",&op,&x);
        if(op==1){
            scanf("%d",&y);
            unite(x,y);
        }
        else{
            printf("%lld\n",cal(x));
        }
    }
	return 0;
}

代码2(路径压缩)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10,mod=998244353;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back
int n,m,op,x,y,par[N],a[N],b[N];//a:主场 b:客场
ll i13,i23;
ll modpow(ll x,ll n,ll mod){
    ll res=1;
    for(;n;n/=2,x=x*x%mod){
        if(n&1)res=res*x%mod;
    }
    return res;
}
//路径压缩 但此时不加上根的贡献
int find(int x){
    if(x==par[x])return x;
    int anc=find(par[x]);
    if(par[x]!=anc){
        a[x]+=a[par[x]];
        b[x]+=b[par[x]];
    }
    return par[x]=anc;
}
//y向x发起挑战 y客场 x主场
void unite(int x,int y){
    x=find(x),y=find(y);
    a[x]++;b[y]++;
    par[x]=y;
    a[x]-=a[y];
    b[x]-=b[y];
}
//3^n * (2/3)^a *(1/3)^b
ll cal(ll a,ll b){
    ll res=modpow(3,n,mod);
    res=res*modpow(i23,a,mod)%mod;
    res=res*modpow(i13,b,mod)%mod;
    return res;
}
int main(){
    i13=modpow(3,mod-2,mod);//1/3
    i23=2ll*i13%mod;//2/3
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        par[i]=i;
    }
    for(int i=1;i<=m;++i){
        scanf("%d%d",&op,&x);
        if(op==1){
            scanf("%d",&y);
            unite(x,y);
        }
        else{
            y=find(x);
            if(x==y)printf("%lld\n",cal(a[x],b[x]));
            else printf("%lld\n",cal(a[x]+a[y],b[x]+b[y]));
        }
    }
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值