union
union
Union 模板
int n = 1005;
vector<int> father = vector<int> (n, 0);
// 并查集初始化
void init() {
for (int i = 0; i < n; ++i) {
father[i] = i;
}
}
// 并查集尋根過程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]); // 路徑壓縮
}
// 判斷 u 和 v是否找到同一個根
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 將v->u 這條邊加入并查集
void join(int u, int v) {
u = find(u); // 尋找u的根
v = find(v); // 尋找v的根
if (u == v) return ; // 如果發現根相同,則在一个集合,不用两個節點相連,直接返回
father[v] = u;
}
Union按rank合併
int n = 1005; // n根據題目定義,一般比節點大一點即可
vector<int> father = vector<int> (n, 0);
vector<int> rank = vector<int> (n, 1); // 初始化每棵樹高度為1
// 并查集初始化
void init() {
for (int i = 0; i < n; ++i) {
father[i] = i;
rank[i] = 1; // 可以省略
}
}
// 并查集尋找根過程
int find(int u) {
return u == father[u] ? u : find(father[u]);// 注意這裏不做路徑壓縮
}
// 判斷 u 和 v是否找到同一個根
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 將v->u 這條邊加入并查集
void join(int u, int v) {
u = find(u); // 尋找u的根
v = find(v); // 尋找v的根
if (rank[u] <= rank[v]) father[u] = v; // rank小的樹合入到rank大的樹
else father[v] = u;
if (rank[u] == rank[v] && u != v) rank[v]++; // 如果两棵樹高度相同,則v的高度+1,因為上面 if (rank[u] <= rank[v]) father[u] = v; 注意是 <=
}
Hdu1213
- 題目要求好友關系,如果A認識B就會做一起,只需要一張桌子,現在來個C不認識自己也一張
- 用union來將認識的合併,在統計有多少集合
- 注意題目從1開始
#include <iostream>
using namespace std;
const int N = 1050;
int s[N];
void init_set(){
for(int i=1;i<=N;i++) s[i] = i;
}
int find_set(int x) {
if(x != s[x]) s[x] = find_set(s[x]);
return s[x];
}
void merge_set(int x,int y){
x=find_set(x);y=find_set(y);
if(x!=y)s[x]=s[y];
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t,m,n,x,y;
cin >> t;
while(t--){
cin >> n >> m;
init_set();
for(int i=1;i<=m;i++){
cin >> x >> y;
merge_set(x,y);
}
int ans=0;
for(int i=1;i<=n;i++){
if(s[i]==i)ans++;
}
cout << ans << '\n';
}
}
帶權并查集
- 路徑壓縮
d[1]' = d[1]+d[2]+d[3]
- 先用t紀錄x的父節點,在遞迴過程中最後返回的是根節點,最後再將當前節點的權重加上原父節點的權重
- 經過遞迴,此時父節點也指向根節點,父節點權重已經更新為父節點直接到根節點權重
int find_set(int x){
if(x != s[x]){
int t = s[x];
s[x] = find_set(s[x]);
d[x] += d[t];
}
return s[x];
}
- 合併
- 把點x和點y合併,就是將x的根節點fx合併到y的根節點fy,在fx和fy之間增加權重
hdu3038
#include <iostream>
using namespace std;
const int N = 200010;
int s[N];
int d[N];
void init_set(int n){
for(int i=0; i<= n;i++) {s[i]=i;d[i]=0;};
}
int find_set(int x){
if(x != s[x]){
int t = s[x];
s[x] = find_set(s[x]);
d[x] += d[t];
}
return s[x];
}
void mearge_set(int a,int b,int v,int&ans){
int roota = find_set(a);
int rootb = find_set(b);
if(roota == rootb) {
if(d[a]-d[b] != v) ans++;
}else{
s[roota]=rootb;
d[roota]=d[b]-d[a]+v;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n,m;
while(scanf("%d%d",&n,&m) != EOF){
init_set(n);
int ans=0;
while(m--){
int a,b,v;
scanf("%d%d%d",&a,&b,&v);
a--;
mearge_set(a,b,v,ans);
}
printf("%d\n",ans);
}
}
poj1182
-
路徑壓縮更新權重規律
- 0表示同類,1表示吃,2表示被吃
d(a->b)=1 d(b->c)=1 d(a->c)=2
d(a->b)=2 d(b->c)=2 d(a->c)=1
d(a->b)=0 d(b->c)=1 d(a->c)=1
-
由規律可發覺
d(a->c)=(d(a->b)+d(b->c))%3
-
relation-1是為了讓區間限制在[0,1,2],因為我們有取mod操作
cin會超時
#include <iostream>
#include <stdio.h>
using namespace std;
const int N = 50005;
int s[N];
int d[N];
int ans;
void init_set(int n){
for(int i=0;i<=n;i++){
s[i]=i;
d[i]=0;
}
}
int find_set(int x){
if(x != s[x]){
int t = s[x];
s[x] = find_set(s[x]);
d[x] = (d[x]+d[t])%3;
}
return s[x];
}
void merage_set(int x,int y,int relation){
int roota = find_set(x);
int rootb = find_set(y);
if(roota==rootb){
if((relation-1)!=((d[x]-d[y]+3)%3))ans++;
}else{
s[roota]=rootb;
d[roota]=(d[y]-d[x]+relation-1)%3;
}
}
int main(){
ios::sync_with_stdio(0);
int n,k;scanf("%d%d",&n,&k);
init_set(n);
ans=0;
while(k--){
int relation,x,y;scanf("%d%d%d",&relation,&x,&y);
if(x>n || y>n || (relation==2&&x==y))ans++;
else merage_set(x,y,relation);
}
printf("%d",ans);
return 0;
}
poj2236
- 從0開始所以要減1
#include <iostream>
using namespace std;
const int N = 1050;
int s[N];
int x[N];
int y[N];
bool m[N];
int r[N];
int find_set(int x){
if(x != s[x]) s[x] = find_set(s[x]);
return s[x];
}
void merage(int a,int b){
int roota = find_set(a);
int rootb = find_set(b);
if(roota != rootb) s[roota] = rootb;
}
int dis(int a,int b){
return (((x[a] - x[b]) * (x[a] - x[b]))+((y[a] - y[b]) * (y[a] - y[b])));
}
int main()
{
int n, d;
cin >> n >> d;
for(int i=1;i<=n;i++){
cin >> x[i-1] >> y[i-1];
s[i] = i;
m[i] = false;
}
char c;
int p;
int idx=0;
while(cin >> c){
if(c == 'O'){
cin >> p;
p--;
r[idx++] = p;
for(int i=0;i<idx;i++){
if(dis(r[i],p) <= d*d) merage(r[i],p);
}
}else if(c =='S'){
int l=0,r=0;
cin >> l >> r;
l--;r--;
int rootl = find_set(l);
int rootr = find_set(r);
rootl == rootr ? cout << "SUCCESS" << '\n' : cout << "FAIL" << '\n';
}
}
}
poj1611
- 加入并查集後找是否有和0相同的根
- 初始化要從0開始!
當一次要輸入很多值進union,可用以下方式
int q, n1;
cin >> q >> n1;
for (int i = 1; i < q; i++)
{
int n;
cin >> n;
merage(n1, n);
n1 = n;
}
#include <iostream>
using namespace std;
const int N = 30050;
int s[N];
void init_set(int n){
for(int i=0;i<n;i++) s[i] = i;
}
int find_set(int x){
if(x != s[x]) s[x]=find_set(s[x]);
return s[x];
}
void merage(int a,int b){
int roota = find_set(a);
int rootb = find_set(b);
if(roota != rootb) s[roota] = rootb;
}
int main(){
int n,m;
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
while(cin >> n >> m){
if(n == 0 && m == 0) break;
init_set(n);
while (m--)
{
int q, n1;
cin >> q >> n1;
for (int i = 1; i < q; i++)
{
int n;
cin >> n;
merage(n1, n);
n1 = n;
}
}
int ans = 0;
for (int i = 0; i < n; i++)
{
if (find_set(s[i]) == find_set(0))
ans++;
}
cout << ans << '\n';
}
return 0;
}
ACM107
#include <iostream>
using namespace std;
const int N = 105;
int s[N];
void init(int n){
for(int i=0;i<n;i++) s[i]=i;
}
int find_set(int x){
if(x != s[x]) s[x] = find_set(s[x]);
return s[x];
}
void merage(int a,int b){
int roota = find_set(a);
int rootb = find_set(b);
if(roota != rootb) s[roota] = rootb;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n,m,s,t,source,destination;
cin >> n >> m;
init(n);
while(m--){
cin >> s >> t;
merage(s,t);
}
cin >> source >> destination;
find_set(source) == find_set(destination) ? cout << 1 << '\n' : cout << 0 << '\n';
}
ACM108
#include <iostream>
using namespace std;
const int N = 1050;
int s[N];
void init(int n){
for(int i =0;i<n;i++)s[i]=i;
}
int find_set(int x){
if(x != s[x]) s[x] = find_set(s[x]);
return s[x];
}
void join(int a,int b){
int roota = find_set(a);
int rootb = find_set(b);
if(roota != rootb) s[roota] = rootb;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n;cin>>n;
init(n);
while(n--){
int s,t;cin>>s>>t;
if(find_set(s)==find_set(t)){
cout << s << " " << t;
break;
}else{
join(s,t);
}
}
return 0;
}
ACM109
- isTreeAfterRemove判斷有邊的情況,之所以先判斷vc[0]是由於放入vc是倒敘的緣故
- 在紀錄邊的時候i從n-1是題目要求從做後一個刪
#include <iostream>
#include <vector>
using namespace std;
int n =0;
vector<int>s(1001,0);
void init_set(){
for(int i=1;i<=n;i++)s[i]=i;
}
int find_set(int x){
if(x != s[x]) s[x] = find_set(s[x]);
return s[x];
}
void join(int x,int y){
int rootx = find_set(x);
int rooty = find_set(y);
if(rootx != rooty) s[rootx] = rooty;
}
bool isTreeAfterRemove(vector<vector<int>>&edges,int deleteEdge){
init_set();
for(int i=0;i<n;i++){
if(i == deleteEdge) continue;
if(find_set(edges[i][0]) == find_set(edges[i][1])){
return false;
}else{
join(edges[i][0],edges[i][1]);
}
}
return true;
}
void getRemoveEdge(vector<vector<int>>&edges){
init_set();
for(int i=0;i<n;i++){
if(find_set(edges[i][0]) == find_set(edges[i][1])){
cout << edges[i][0] << " " << edges[i][1];
return;
}else{
join(edges[i][0], edges[i][1]);
}
}
}
int main(){
vector<vector<int>> edges;
cin >> n;
vector<int>isDrgee(n+1,0);
for(int i=0;i<n;i++){
int s,t;cin >> s >> t;
// 紀錄節點
edges.push_back({s,t});
// 紀錄邊為2的節點
isDrgee[t]++;
}
vector<int> vc;
//紀錄邊為2的入vc等刪除
for(int i=n-1;i >=0;i--){
if(isDrgee[edges[i][1]]== 2){
vc.push_back(i);
}
}
// 邊為2的要判斷刪除後是否還是樹
if(vc.size() > 0){
if(isTreeAfterRemove(edges,vc[0])){
cout << edges[vc[0]][0] << " " << edges[vc[0]][1];
}else{
cout << edges[vc[1]][0] << " " << edges[vc[1]][1];
}
return 0;
}
// 沒有邊為2的必定有有向環,處理有向環即可返回
getRemoveEdge(edges);
}