並查集 1.判斷成環否
阿新 • • 發佈:2022-05-15
注意輸入!!
並查集
注意更改合併根節點數量 要先更改數量 然後再合併結點
tle 請看看自己查父函式有沒有return
或者使用
int find(int x)//找到x的祖宗節點。
{
if(x!=p[x])
p[x]=find(p[x]);
return p[x];
}//這樣寫對查詢的某個點 進行了轉化p[x]=根節點
集合的數量 在根節點上儲存 所以合併之後更新根節點
帶權並查集 :只有一個樹根節點但會變 需要維護書中的距離
判斷是否成環 https://www.acwing.com/activity/content/problem/content/1579/
題目給的是一個個 不同的點的時候 問給出點之後能不能成環 成環需要 P[a]==p[b]
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 40010; int p[N]; int n,m; int find(int x){ if(p[x]!=x){ p[x]=find(p[x]); } return p[x]; } int get(int a,int b){ return a*n+b; } int main() { cin >> n>>m; for (int i = 0; i < n*n; i ++ ){ p[i]=i; } string op; int res=0; for (int i = 1; i <= m; i ++ ){ int x,y;cin>>x>>y>>op; x--,y--; int a=get(x,y); int b; if(op=="D"){ b=get(x+1,y); }else{ b=get(x,y+1); } int pa=find(a),pb=find(b); if(pa==pb){ res=i; break; } p[pa]=pb; } if(!res) cout << "draw"; else cout << res; return 0; }
連通塊+01揹包
int n, m, vol; int v[N], w[N]; int p[N]; int f[N]; int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } int main() { cin >> n >> m >> vol; for (int i = 1; i <= n; i ++ ) p[i] = i; for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i]; while (m -- ) { int a, b; cin >> a >> b; int pa = find(a), pb = find(b); if (pa != pb) { v[pb] += v[pa]; w[pb] += w[pa]; p[pa] = pb; } } // 01揹包 for (int i = 1; i <= n; i ++ ) if (p[i] == i) for (int j = vol; j >= v[i]; j -- ) f[j] = max(f[j], f[j - v[i]] + w[i]); cout << f[vol] << endl; return 0; }
離散化+並查集 https://www.acwing.com/problem/content/239/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e6+10;
int n,m;
int p[N];
unordered_map<int,int>s;
struct que{
int x,y,e;
}query[N];
int find(int x){
if(x!=p[x]){
p[x]=find(p[x]);
}
return p[x];
}
int get(int x){
if(s.count(x)==0) s[x]=++n;
return s[x];
}
int main()
{
int t;cin>>t;
ios::sync_with_stdio(false);
cin.tie(0);
while(t--){
n=0;s.clear();
cin >> m;
for (int i = 0; i < m; i ++ ){
int x,y,e;
cin >> x>>y>>e;
query[i]={get(x),get(y),e};
}
for (int i = 1; i <= n; i ++ ) p[i]=i;
for (int i = 0; i < m; i ++ ){
if(query[i].e==1){
int pa=find(query[i].x),pb=find(query[i].y);
p[pa]=pb;
}
}
bool flag=false;
for (int i = 0; i < m; i ++ ){
if(query[i].e==0){
int pa=find(query[i].x),pb=find(query[i].y);
if(pa==pb){
flag=true;
break;
}
}
}
if(flag) puts("NO");
else puts("YES");
}
return 0;
}
帶邊權並查集 離散化+帶邊權
https://www.acwing.com/problem/content/241/
複雜的 麻煩的我吐了 。。。
const int N = 20010;
int n, m;
int p[N], d[N];
unordered_map<int, int> S;
int get(int x)
{
if (S.count(x) == 0) S[x] = ++ n;
return S[x];
}
int find(int x)
{
if (p[x] != x)
{
int root = find(p[x]);//root變成了根節點 因為後面return 是找到根的時候了
d[x] += d[p[x]];//先讓距離增加
p[x] = root;//再設定父節點為根節點
}
return p[x];
}
int main()
{
cin >> n >> m;
n = 0;
for (int i = 0; i < N; i ++ ) p[i] = i;
int res = m;
for (int i = 1; i <= m; i ++ )
{
int l, r;
string type;
cin >> l >> r >> type;
a = get(l - 1), b = get(r);
int t = 0;
if (type == "odd") t = 1;//是1類
int pa = find(a), pb = find(b);
if (pa == pb)//在同一個集合裡面
{
if (((d[a] + d[b]) % 2 + 2) % 2 != t)//看看是否矛盾
{
res = i - 1;
break;
}
}
else//之前沒有這個l-1到r的關係,現在要結合在一起了
{
p[pa] = pb;
d[pa] = d[a] ^ d[b] ^ t;//兩類集合之間的那個距離? 若da^?^db=1類 ?=1^da&db
}
}
cout << res << endl;
return 0;
}
網路分析https://www.acwing.com/activity/content/code/content/603227/
題意:一開始的點都為獨立的點 完成連線之後的獨立的點在連線之前加上的值 單獨算
難點 :怎麼單獨獨立算 ( 連線之前讓另一個連線節點減去本身的值 那麼其下面的節點往上找就不會加錯 或 加一個空節點) 不是根節點=d[i]+d[find(i)]
d[x]陣列連線的時候怎麼處理:細節
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010;
int n, m;
int p[N], d[N];
int find(int x)
{
if (p[x] == x || p[find(p[x])] == p[x]) return p[x];//x是根點點 或者 x父節點是根節點 都可以通過返回px表示根節點
int r = find(p[x]);
d[x] += d[p[x]];
p[x] = r;
return r;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
while (m -- )
{
int t, a, b;
scanf("%d%d%d", &t, &a, &b);
if (t == 1)
{
a = find(a), b = find(b);
if (a != b)//原來不是一個根節點的時候才這麼處理
{
d[a] -= d[b];//關鍵連線之前先減去這個值
p[a] = b;
}
}
else
{
a = find(a);
d[a] += b;
}
}
for (int i = 1; i <= n; i ++ )
if (i == find(i)) printf("%d ", d[i]);//這一步必須要find(i);//如果1->2->3 但是第一步合併2,4 然後又在4上面價值 那麼變成1->2->4 3->4 1不是直接和4相連所以需要find一下 d[i]+d[find(i)]是建立在i直接連的根節點基礎上
else printf("%d ", d[i] + d[find(i)]);
puts("");
return 0;
}
修改陣列https://www.acwing.com/problem/content/1244/
![]
(https://img2022.cnblogs.com/blog/2525875/202204/2525875-20220405231857775-32125794.png)
p[x]裡面的根節點 x指向下一個應該填的數
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e6;
int p[N];
int find(int x){
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
int main()
{
int n;cin>>n;
for (int i = 1; i < N; i ++ ){
p[i]=i;
}
for (int i = 0; i < n; i ++ ){
int x;cin>>x;
x=find(p[x]);
p[x]=x+1;
cout << x<<" ";
}
return 0;
}
題目好朋友
const int n=110;
int father[N];//用於查詢,下標是某個節點,值是存放父親結點
int isRoot[N];//存放每個集合的代表結點
int findfather(int x)//尋找父親的函式
{ //int a=x;//為了路徑壓縮
while(x!=father[x]){
x=father[x];
}
//while(a!=father[a]){ //路徑壓縮
// int z=a;
// a=father[a];
// father[z]=a;
//}
return x
void union(int a,int b){
int faA=findfather(a);//表示根節點
int faB=findfather(b);//表示根結點
if(faA!=faB){
father[faA]=faB;//將a最上面結點的父親從自己設為b最上面的父親
}
}
void init(int n){
for(int i=1;i<n;i++){
father[i]=i;
isRoot[i]=false;//該結點的代表
}
}
}
int n,m,a,b;
cin<<n<<m;
init(n);//n表示總共有的人進行初始話
for(int i=0;i<m;i++){//m表示為所在的集合
cin<<a<<b;
union(a,b);
}
for(int i=1;i<=n;i++){//只有跟結點才設為真
isroot[findfather(i)]=true;
}
int ans=0;
for(int i=1;i<=n;i++){//統計
ans+=isroot[i];
}
cout<<ans;
return 0;
計算連通塊
int calblock(int n){//傳入一個值
int block=0;
for(int i=1;i<=n;i++){
root[findfathher(i)]=true;//讓根節點變為1
}
for(int i=1;i<=n;i++){
block+=root[i];//然後加上這個就行了
}
return block;
}
#include <bits/stdc++.h>
using namespace std;
int father[5005];
int isroot[5005];
int findfather(int x){//尋父親函式
// int a=x;
while(x!=father[x])
x=father[x];
//while(a!=father[a]){
// int z=a;
// a=father[a];
// father[z]=x;
}
void Union(int a,int b){//聯合函式
int faA=findfather(a);
int faB=findfather(b);
if(faA!=faB){
father[faA]=faB;
}
}
void init(int n){//初始話函式
for(int i=1;i<=n;i++){
father[i]=i;
isroot[i]=false;
}
}
int main()
{
int n,m,p;
cin>>n;
init(n);//初始話
int a,b;
cin>>m; cin>>p;
for(int i=1;i<=m;i++){
cin>>a>>b;
Union(a,b);//聯合
}
for(int i=1;i<=p;i++){//分析每一對
cin>>a>>b;
if(findfather(a)==findfather(b))
cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
洛谷:修復公路,連成幾個的問題,都是用n--,當n==某值的時候輸出
#include <bits/stdc++.h>
using namespace std;
int father[5005];
int isroot[5005];
struct way{
int x,y,z;
}a[100005];//設計到結構體的排序問題
int findfather(int x)
{ int a=x;
while(x!=father[x])
x=father[x];
while(a!=father[a]){
int z=a;
a=father[a];
father[z]=x;
}
}
void Union(int x,int y){
int faX=findfather(x);
int faY=findfather(y);
if(faX!=faY)
father[faX]=faY;
}
void init(int n){
for(int i=1;i<=n;i++){
isroot[i]=false;
father[i]=i;
}
}
bool cmp(way a ,way b){
return a.z<b.z;
}
int main()
{
int n,m,time=0;
cin>>n>>m;
init(n);
int z,x,y;
for(int i=1;i<=m;i++){
cin>>a[i].x>>a[i].y>>a[i].z;
}
sort(a ,a+m+1 , cmp);
// for(int i=1;i<=m;i++) cout<<a[i].z<<" ";
for(int i=1;i<=m;i++){
if(findfather(a[i].x)!=findfather(a[i].y)){
Union(a[i].x,a[i].y);//只要不是同一個集合就聯合起來
n--;
}
if(n==1) {//發現只剩一個村莊的時候,說明已經成為了一個整體
cout<<a[i].z;//因為修路可以同時進行
return 0;
}
}
cout<<"-1";
return 0;
}
通訊系統http://codeup.hustoj.com/problem.php?cid=100000615&pid=0
有小坑:額外附加了一個條件,單個端點只接收一次訊息,所以,不能有環出現,
只有不是環的時候才能輸出yes
怎麼判斷?對圖進行樹化,只能根據樹的邊數為n-1定則,而且要所有端點必須為同一集合,那麼M必須等於N-1
找個數 輸出王先生可以保留的最大男孩數量。(即有多少個人,在房間裡的人都是朋友在房間裡)
#include <cstdio>
#include <vector>
#include <utility>
using namespace std;
const int maxn=10000010;
int father[maxn]={0},num[maxn];
int maxnum;
int findFather(int a)
{
int x=a;
while(x!=father[x])
{
x=father[x];
}
while(a!=father[a])
{
int z=a;
a=father[a];
father[z]=x;
}
return x;
}
void Union(int a, int b)
{
int A=findFather(a);
int B=findFather(b);
if(A!=B)//不是一個集合的話 有變動 注意
{
father[A]=B;//選擇將一個根結點的父親變為另一根結點
num[B]+=num[A];//讓另一個根結點的數值增加,
if(maxnum<num[B]) 並且更新最大值
maxnum=num[B];
}(要選出每個集合中個數的最大值)
}
void init(int n)
{
father[n]=n;
num[n]=1;
}
int main()
{
int n,dot1,dot2;
vector<pair<int,int> > input;
while(~scanf("%d",&n))
{
if(n==0)
{
printf("1\n");
continue;
}
input.clear();
for(int i=0;i<n;++i)
{
scanf("%d %d",&dot1,&dot2);//輸入每對的資訊
init(dot1);//一個個人初始化節省空間?
init(dot2);
input.push_back(make_pair(dot1,dot2));
}
maxnum=0;
for(int i=0;i<input.size();++i)
{
Union(input[i].first,input[i].second);//聯合
}
printf("%d\n",maxnum);
}
return 0;
查詢圖的聯通塊
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
vector<int>graph[N];
bool vis[N];
int isfather[N];
int isroot[N];
int findfather(int i){
int x=i;
while(x!=isfather[x]){
x=isfather[x];//父節點=等於祖宗結點
}
while(i!=isfather[i]){
int n=i;
i=isfather[i];
isfather[n]=x;
}return x;
}
void combine(int x,int y){
int faa=findfather(x);
int fab=findfather(y);
if(faa!=fab){
isfather[faa]=fab;
}
}
void init(){
for(int i=0;i<N;i++){
isfather[i]=i;
vis[i]=false;
}
}//查父合併初始話
int main(){
int n,m,k;
cin>>n>>m>>k;
for(int i=0;i<m;i++){
int x,y;
cin>>x>>y;
graph[x].push_back(y);
graph[y].push_back(x);
}
int currentpoint;
for(int i=0;i<k;i++){
cin>>currentpoint;
init();
for(int i=0;i<=n;i++){//遍歷每個頂點,列舉每條邊,合併每個邊
for(int j=0;j<graph[i].size();j++){
if(i==currentpoint||graph[i][j]==currentpoint) continue;
combine(i,graph[i][j]);
}
}
int block=0;
for(int i=1;i<=n;i++){//你仍然需要遍歷所有的頂點
if(i==currentpoint) continue;
int fa_i=findfather(i);//對於這個根節點如果沒有遍歷過就說明是另外一個塊
if(vis[fa_i]==false){
block++;
vis[fa_i]=true;
}
}
if(block>0)
cout<<block-1 <<endl;
else
cout<<block <<endl;
}
return 0;
}
字串歸類 https://www.acwing.com/problem/content/4307/
字串 只要由相同的字母就可以歸為一類 所有字串被劃分為多少類
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5+10;
int p[N],n;
int id[26];
char str[55];
int find(int x){
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) p[i]=i;int res=0;
for (int i = 1; i <= n; i ++ ){
cin>>str;
for (int j = 0; str[j]; j ++ ){
int x=str[j]-'a';
if(id[x]){
p[find(id[x])]=i;//如果這個字母出現過 就把這個字母的之前出現的字串合併到當前出現的字串
}
else id[x]=i;
}
}
for(int i=1;i<=n;i++) if(p[i]==i) res++;;
cout << res;
}
帶權並查集 食物鏈
#include <iostream>
using namespace std;
const int N = 50010;
int n, m;
int p[N], d[N];
1 -> 2 -> 3-> 跟
1 -> 2 -> 跟
d[x] d [p [x] ]
1-> 跟
int find(int x)
{
if (p[x] != x)//不是根節點
{
int t = find(p[x]);//存放根節點
d[x] += d[p[x]];//d[px]存放的是到根節點的距離,因為上一步遞迴把p[x]變成了根節點
p[x] = t;//把
}
return p[x];
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
int res = 0;
while (m -- )
{
int t, x, y;
scanf("%d%d%d", &t, &x, &y);
if (x > n || y > n) res ++ ;
else
{
int px = find(x), py = find(y);//px表示x的根節點
if (t == 1)
{
if (px == py && (d[x] - d[y]) % 3) res ++ ;//如果現在的兩個結點已經連線到一起了 計算他們的距離 如果是同類 那麼到3的距離餘的距離應該相等位0 這裡為1說明不成立是假話
else if (px != py)//如果沒有連線到一起而且必是真話 需要連線到一起
{
p[px] = py;//把py便成根節點
d[px] = d[y] - d[x];//如果同類 那麼 d[x]+d[p[x]] == d[y] 在模3的意義下相等 所以反推出d[x]
}
}
else//設x能吃y 那麼x比y少1 1吃0
{
if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;//如果x吃y 那麼x和 y+1在模3 的意義下相等 這裡餘數不為0 數說明是假話
else if (px != py)//必然是真話
{
p[px] = py;
d[px] = d[y] + 1 - d[x];//如果x吃y d[x]+d[px] == d[y]-1 在3的意義下
}
}
}
}
printf("%d\n", res);
return 0;
}