AT_abc447_E 并查集的应用
题目大意,就是给你一个图,要求这个图的联通块要等于两个,你可以做的操作是把其中的某几条线给删除掉

*输入
5 7
2 3
1 2
1 5
4 5
2 4
3 5
1 3
输出
22
解题思路
每条边的边权时2^i次方(1<=i<=m)
策略是要尽可能的保住边权大的那条边,之所以从开始就是为了保住i更大的一方,因为27>26+25+24…………,所以保住i更大的也就是边权更大的那一条边非常重要
这个题目跟联通块相关联,所以非常适合用并查集(我自己这么觉得的)
code
#include<bits/stdc++.h>
using namespace std;
//"O campeão tem nome, e se chama Charles Oliveira!"
#define int long long
#define endl '\n'
#define emplace ep
#define pob
#define ll long long
#define pb push_back
#define pof pop_front
#define pob pop_back
#define all(a) a.begin(),a.end()
#define rall(a) a.rbegin(),a.rend()
using ld = long double;
using ui = unsigned;
using ull = unsigned long long;
using i128 = __int128;
const int N=200005;
int mod=998244353;
int MOD=1000000007;
int n,m,u[N],v[N],ans,vis[N],fa[N];
void init(int n){
for(int i=1;i<=n;i++){
fa[i]=i;
}
return ;
}
int find(int x){
return(fa[x]==x?x:fa[x]=find(fa[x]));
}
void uni(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx!=fy){
fa[fx]=fy;
}
return ;
}
int fast_pow(int n,int x){
int res=1;
while(x){
if(x&1){
res=(res*n)%mod;
}
n=(n*n)%mod;
x>>=1;
}
return res%mod;
}
void solve() {
cin>>n>>m;
init(n);
for(int i=1;i<=m;i++){
cin>>u[i]>>v[i];
}
//之所以从开始就是为了保住i更大的一方,因为2^7>2^6+2^5+2^4…………
//所以保住i更大的也就是边权更大的那一条边非常重要
for(int i=m,cnt=n;i>=1;i--){
if(cnt>2&&find(u[i])!=find(v[i])){
uni(fa[u[i]],fa[v[i]]);
cnt--;
vis[i]=1;//这个不去掉
}
else if(find(u[i])==find(v[i])){
//如果父亲节点都是一样的话,去掉和不去掉都不会影响联通块的数量
vis[i]=1;
}
}
for(int i=1;i<=m;i++){
if(vis[i]==0){
ans=(ans+fast_pow(2,i))%mod;
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t=1;
//cin>>t;
while(t--)solve();
}
代码解释
先是并查集的标准开头
void init
void find
void unit
核心代码及注释
for(int i=m,cnt=n;i>=1;i--){
if(cnt>2&&find(u[i])!=find(v[i])){
uni(fa[u[i]],fa[v[i]]);//当cnt>2的时候,这个大的边可以留着,对于大的边,只要可以留那就留
//这两个的父节点不一样的话,那就直接把这两个连接起来,然后cnt--,vis[1]代表保留
cnt--;
vis[i]=1;//这个不去掉
}
else if(find(u[i])==find(v[i])){
//如果父亲节点都是一样的话,去掉和不去掉都不会影响联通块的数量
vis[i]=1;
}
}

浙公网安备 33010602011771号