at_abc314_f 题解
题意:
有 \(n\) 名球员,进行 \(n-1\) 场比赛。对于每场比赛,给定信息 \(p,q\)。那么这场比赛的参赛方为编号为 \(p, q\) 的球员所在的队伍,设为 \(a,b\) 队,分别有 \(|a|,|b|\) 人。那么 \(a\) 队与 \(b\) 对的获胜概率分别为 \(\dfrac{|a|}{|a|+|b|}\) 与 \(\dfrac{|b|}{|a|+|b|}\)。每场比赛后参赛两队将合并为一支新的队伍。现请求出拥有第 \(i\) 号球员的队伍的获胜概率的期望。对 \(998244353\) 取模。
分析:
对于每一个点,我们需要维护他所属的队伍与他的队伍的获胜信息,考虑使用并查集维护所属集合和路径上的信息。具体操作为:对于 \(p, q\) 查他在并查集上的祖先 \(f_p, f_q\)。计算概率并将 \(tag\) 打在祖先上。这样对于每一个点,我们只需要一直查他的祖先,并将路径上的 \(tag\) 值加起来,则得到一个点的答案。
由于需要路径上的信息,不建议使用路径压缩。但是看一眼数据范围,暴力是过不了的,因此使用按秩合并。数据范围不需要我们线性地求逆元,只需要快速幂即可。
注意:在合并 \(x, y\) 两个集合时(例如:将 \(x\) 集并在 \(y\) 集上),由于合并前 \(y\) 集的信息不应包含 \(x\) 集。因此在合并时应给 \(tag_x\) 减上 \(tag_y\) 这样能保证查出来的数据是正确的。
代码:
#include<bits/stdc++.h>
#define int long long //没把握就define全局long long
using namespace std;
int n, p, q; const int mod = 998244353;
int siz[200005], fa[200005], tag[200005];//并查集数组
inline int find(int x){ //查祖先
if(x == fa[x]) return x;
return find(fa[x]);
}
inline void merge(int x, int y){ //合并
if(siz[x] <= siz[y]){ //按秩合并
fa[x] = y;
siz[y] += siz[x];
tag[x] = (tag[x] - tag[y] + mod) % mod; //记得减tag和取模
}
else{
fa[y] = x;
siz[x] += siz[y];
tag[y] = (tag[y] - tag[x] + mod) % mod;
}
}
inline int qpow(int a, int b){
int res = 1;
while(b){
if(b & 1) res = res * a % mod;
b >>= 1; a = a * a % mod;
}return res;
}
inline int cacl(int p){ //计数
int res = 0;
while(p != fa[p]) res = (res + tag[p]) % mod, p = fa[p];
return (res + tag[p])%mod; //别忘了加根
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0); cin >> n;//一定注意要读入(麻赛时没读入wa了两发最后也没调出来)
for(int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1; //初始化
for(int i = 1 && cin >> p >> q; i < n; i++ && cin >> p >> q){ //顺带读入
int x = find(p), y = find(q);
int sum = siz[x] + siz[y]; int inv = qpow(sum, mod - 2); //逆元
tag[x] = (tag[x] + siz[x] * inv) % mod;
tag[y] = (tag[y] + siz[y] * inv) % mod; //打tag
merge(x, y); //合并
}
for(int i = 1; i <= n; i++){
cout << cacl(i) << ' ';
}return 0;
}

浙公网安备 33010602011771号