并查集扩展

并查集扩展

普通并查集

例题:

1.洛谷P1197 星球大战

链接:

[P1197 JSOI2008] 星球大战 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

类型:

普通并查集+反向加点

解析:

正面删边很难,考虑反向求解,先求删完所有要求点的所有连通块数量,然后再一个一个加入,将问题简化

代码:

highlighter- cpp

    
    const int N = 400005;

    
    struct edges{

    
    	int v,ne;

    
    }e[N << 1];

    
    int h[N],idx = 0;

    
    void add(int u,int v){

    
    	e[idx] = {v,h[u]};

    
    	h[u] = idx++;

    
    }

    
    int f[N];

    
    int fd(int x){

    
    	if(x==f[x]) return x;

    
    	return f[x] = fd(f[x]);

    
    }

    
    int vis[N],ans[N];

    
    int n,m;

    
    void solve(){

    
    	memset(h,-1,sizeof h);

    
    	cin >> n >> m;

    
    	for(int i = 1;i<=n;i++) f[i] = i;

    
    	vector<int> b;

    
    	for(int i = 1;i<=m;i++){

    
    		int u,v;

    
    		cin >> u >> v;

    
    		u++,v++;

    
    		add(v,u);

    
    		add(u,v); 

    
    	}

    
    	int k;

    
    	cin >> k;

    
    	int res = n - k;

    
    	for(int i = 0;i<k;i++){

    
    		int t;

    
    		cin >> t;

    
    		t++;

    
    		vis[t] = 1;

    
    		b.push_back(t);

    
    	}

    
    	for(int i = 1;i<=n;i++){

    
    		if(vis[i]) continue;

    
    		for(int j = h[i];~j;j=e[j].ne){

    
    			int v = e[j].v;

    
    			if(vis[v]) continue;

    
    			if(f[fd(i)] != f[fd(v)]){

    
    				res--;

    
    				f[fd(i)] = f[fd(v)];

    
    			}

    
    		}

    
    	}

    
    	ans[k+1] = res;

    
    	for(int i = k - 1;i >= 0;i--){

    
    		vis[b[i]] = 0;

    
    		res++;

    
    		for(int j = h[b[i]];~j;j=e[j].ne){

    
    			int v = e[j].v;

    
    			if(vis[v]) continue;

    
    			if(f[fd(b[i])] != f[fd(v)]){

    
    				res--;

    
    				f[fd(b[i])] = f[fd(v)];

    
    			}

    
    		}

    
    		ans[i + 1] = res;

    
    	}

    
    	for(int i = 1;i<=k+1;i++){

    
    		cout << ans[i] << endl;

    
    	}

    
    }
2.洛谷P1955 程序自动分析

链接:

[P1955 NOI2015] 程序自动分析 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

类型:

普通并查集+离散化

解析:

普通并查集判断是否矛盾,数据大进行离散化

代码:

highlighter- arduino
   
   const int N = 200005;

   
   struct DSU {

   
       vector<int> f;

   
       DSU(){}

   
       DSU(int n) {

   
           init(n);

   
       }

   
       void init(int n) {

   
           f.resize(n);

   
           iota(f.begin(), f.end(), 0);

   
       }

   
       int fd(int x) {

   
           if(f[x]==x) return x;

   
           return f[x] = fd(f[x]);

   
       }

   
       bool same(int x, int y) {

   
           return fd(x) == fd(y);

   
       }

   
       bool mg(int x, int y) {

   
           x = fd(x);

   
           y = fd(y);

   
           if (x == y)return false;

   
           f[y] = x;

   
           return true;

   
       }

   
   };

   
   int gt_idx(vector<int>& a,int i){

   
   	int t = lower_bound(a.begin(),a.end(),i) - a.begin() + 1;

   
   	return t;

   
   }

   
   void solve(){

   
   	int n;

   
   	cin >> n;

   
   	vector<int> v1;

   
   	vector<pair<int,int>> ys;

   
   	vector<pair<int,int>> ns;

   
   	vector<int> a;

   
   	for(int i = 0;i < n;i++){

   
   		int u,v,op;

   
   		cin >> u >> v >> op;

   
   		if(op==1){

   
   			ys.push_back({u,v});

   
   		}else{

   
   			ns.push_back({u,v});

   
   		}

   
   		a.push_back(u);

   
   		a.push_back(v);

   
   	}

   
   	sort(a.begin(),a.end());

   
   	a.erase(unique(a.begin(),a.end()),a.end());

   
   	int sz = a.size();

   
   	DSU d(sz + 1);

   
   	for(auto &[u,v]:ys){

   
   		d.mg(gt_idx(a,u),gt_idx(a,v));

   
   	}	

   
   	for(auto &[u,v]:ns){

   
   		if(d.fd(gt_idx(a,u)) == d.fd(gt_idx(a,v))){

   
   			cout << "NO" << endl;

   
   			return;

   
   		}

   
   	}

   
   	cout << "YES" << endl;

   
   }

带权并查集

例题:

1.洛谷P2024 食物链

链接:

[P2024 NOI2001] 食物链 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

类型:

带权并查集

解析:

维护%3关系,考虑合并时候维护集合关系,在同类时候d[u] - d[v]在%3意义下一定是0,x吃y那么一定是1,题目无需考虑2的情况,因为没有设置y吃x的正确性

代码:

highlighter- cpp

    
    const int N = 200005;

    
    int f[N],d[N];

    
    int n,m;

    
    int fd(int x){

    
    	if(x!=f[x]){

    
    		int t = f[x];

    
    		f[x] = fd(f[x]);

    
    		d[x] += d[t];

    
    	} 

    
    	return f[x];

    
    }

    
    int calc(int x,int y){

    
    	return ((x-y)%3 + 3)%3;

    
    }

    
    void merge(int x,int y,int v){

    
    	int px = fd(x),py = fd(y);

    
    	f[px] = py;

    
    	d[px] = d[y] - d[x] + v;

    
    }

    
    void solve(){

    
    	cin >> n >> m;

    
    	iota(f,f+n+1,0);

    
    	int cnt = 0;

    
    	while(m--){

    
    		int u,v,op;

    
    		cin >> op >> u >> v;

    
    		if(u>n||v>n){

    
    			cnt++;

    
    			continue;

    
    		}

    
    		if(op==1){

    
    			if(fd(u)==fd(v) && calc(d[u],d[v]) != 0){

    
    				cnt++;

    
    				continue;

    
    			}

    
    			//d[v] = d[u] + d[fd(u)]

    
    			//d[fd[u]] = d[v] - d[u]

    
    			if(fd(u)!=fd(v)) merge(u,v,0);

    
    		}else{

    
    			if(fd(u)==fd(v) && calc(d[u],d[v]) != 1){

    
    				cnt++;

    
    				continue;

    
    			}

    
    			//d[v] = d[u] - 1  + d[fd(u)]

    
    			//d[fd[u]] = d[v] - d[u] + 1

    
    			if(fd(u)!=fd(v)) merge(u,v,1);

    
    		}

    
    	}

    
    	cout << cnt << endl;

    
    }
2.洛谷P1196 银河英雄传说

链接:

[P1196 NOI2002] 银河英雄传说 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

类型:

带权并查集

解析:

维护两个变量,一个是集合大小,一个是元素到根的距离,然后操作即可

代码:

highlighter- cpp

    
    const int N = 200005;

    
    int sz[N],d[N],f[N];

    
    void init(){

    
    	for(int i = 0; i < N - 1;i++){

    
    		f[i] = i;

    
    		sz[i] = 1;

    
    	}

    
    }

    
    int fd(int x){

    
    	if(f[x]!=x){

    
    		int t = f[x];

    
    		f[x] = fd(f[x]);

    
    		d[x] += d[t];

    
    	}

    
    	return f[x];

    
    }

    
    void merge(int x,int y){

    
    	int px = fd(x),py = fd(y);

    
    	f[px] = py;

    
    	d[px] = sz[py];

    
    	sz[py] += sz[px];

    
    }

    
    void solve(){

    
    	int q;

    
    	init();

    
    	cin >> q;

    
    	while(q--){

    
    		int x,y;

    
    		char op;

    
    		cin >> op >>x >> y;

    
    		if(op=='C'){

    
    			if(fd(x)!=fd(y)){

    
    				cout <<-1<<endl;

    
    			}else{

    
    				cout << abs(d[x]-d[y]) - 1 << endl;

    
    			}

    
    		}else{

    
    			merge(x,y);

    
    		}

    
    	}

    
    }
3.洛谷P5937 Parity Game

链接:

[P5937 CEOI1999] Parity Game - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

类型:

带权并查集

解析:

前缀和思想,合并 y 与 x - 1 判断奇偶性即可

代码:

highlighter- cpp

    
    const int N = 200005;

    
    int n,q;

    
    int idx = 0;

    
    map<int,int> h;

    
    int gt(int x){

    
    	if(h.count(x)) return h[x];

    
    	h[x] = ++idx;

    
    	return idx;

    
    }

    
    int d[N],f[N];

    
    int fd(int x){

    
    	if(f[x] != x){

    
    		int t = f[x];

    
    		f[x] = fd(f[x]);

    
    		d[x] += d[t];

    
    	}

    
    	return f[x];

    
    }

    
    void merge(int x,int y,int v){

    
    	int px = fd(x),py = fd(y);

    
    	f[px] = py;

    
    	d[px] = d[y] - d[x] + v;

    
    }

    
    int calc(int x,int y){

    
    	return ((d[x] - d[y])%2 + 2)%2;

    
    }

    
    void solve(){

    
    	cin >> n >> q;

    
    	int res = q;

    
    	for(int i = 0;i<N;i++) f[i] = i;

    
    	int id = 0;

    
    	while(q--){

    
    		int x,y;

    
    		++id;

    
    		string op;

    
    		cin >> x >> y >> op;

    
    		if(x==y&&op=="even"){

    
    			res = id-1;

    
    			break;

    
    		}

    
    		x = gt(x - 1);

    
    		y = gt(y);

    
    		if(op=="even"){

    
    			if(fd(x)==fd(y)){

    
    				if(calc(x,y) == 1){

    
    					res = id - 1;

    
    					break;

    
    				}

    
    			}else{

    
    				merge(x,y,0);

    
    			}

    
    		}else{

    
    			if(fd(x)==fd(y)){

    
    				if(calc(x,y) == 0){

    
    					res = id - 1;

    
    					break;

    
    				}

    
    			}else{

    
    				merge(x,y,1);

    
    			}

    
    		}

    
    	}

    
    	cout << res << endl;

    
    }

扩展域并查集

例题:

1.洛谷P1525 关押罪犯

链接:

[P1525 NOIP2010 提高组] 关押罪犯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

类型:

扩展域并查集 + 带权并查集

解析:

方法一(扩展域并查集):

  • 两个集合,从大到小排序,考虑将敌对两个人分成两个集合,并且扩展域合并,敌人的敌人是朋友,就是设x有两个敌人 y z, 那么 z 与 x+n 建边, x +n 与 y 建边,通过 x+n这个虚点去连接应该在一个集合中的y与z,如果在同一集合,并且是敌对,那么直接输出即可

方法二(带权并查集):

  • 从大到小排序,避免前面的数在一个监狱,将两个集合合并起来,并且赋权值为1代表两个数不在同一监狱,于是在后面我们可以判断,如果两个数之前已经合并过并且mod操作位0代表,这两个集合一定要在一个监狱中,那么直接输出w,(否则,w会更大)

代码:

方法一:

highlighter- arduino
   
   int n,m;

   
   struct qs{

   
   	int u,v,w;

   
   	bool operator<(const qs& q1)const{

   
   		return w > q1.w;

   
   	}

   
   }; 

   
   struct DSU {

   
       vector<int> f, sz;

   
       DSU(){}

   
       DSU(int n) {

   
           init(n);

   
       }

   
       void init(int n) {

   
           f.resize(n);

   
           iota(f.begin(), f.end(), 0);

   
           sz.assign(n, 1);

   
       }

   
       int fd(int x) {

   
           if(f[x]==x) return x;

   
           return f[x] = fd(f[x]);

   
       }

   
       bool mg(int x, int y) {

   
           x = fd(x);

   
           y = fd(y);

   
           if (x == y)return false;

   
           f[y] = x;

   
           return true;

   
       }

   
   };

   
   

   
   void solve(){

   
   	cin >> n >> m;

   
   	DSU d(2*(n+1));

   
   	vector<qs> q;

   
   	for(int i = 0; i < m;i++){

   
   		int u,v,w;

   
   		cin >> u >> v >> w;

   
   		q.push_back({u,v,w});

   
   	}

   
   	sort(q.begin(),q.end());

   
   	for(int i = 0;i < q.size();i++){

   
   		int u = q[i].u,v = q[i].v,w = q[i].w;

   
   		if(d.fd(u) == d.fd(v)){

   
   			cout << w <<endl;

   
   			return;

   
   		}

   
   		d.mg(u,v+n);

   
   		d.mg(u+n,v);

   
   	}

   
   	cout << 0 << endl;

   
   }

方法二:

highlighter- arduino
   
   struct qs

    
    {

   
   	int u,v,w;

   
   	bool operator<(const qs &t)const{return w > t.w;};

   
   };

   
   int f[N],d[N];

   
   int n,m;

   
   int fd(int x){

   
   	if(x!=f[x]){

   
   		int t = f[x];

   
   		f[x] = fd(f[x]);

   
   		d[x] += d[t];

   
   	} 

   
   	return f[x];

   
   }

   
   int calc(int x,int y){

   
   	return ((x-y)%2 + 2)%2;

   
   }

   
   void merge(int x,int y){

   
   	int px = fd(x),py = fd(y);

   
   	f[px] = py;

   
   	d[px] = d[y] - d[x] + 1;

   
   }

   
   void solve(){

   
   	cin >> n >> m;

   
   	int q = m;

   
   	vector<qs> a;

   
   	iota(f,f+n+1,0);

   
   	while(q--){

   
   		int u,v,w;

   
   		cin >> u >> v >> w;

   
   		a.push_back({u,v,w});

   
   	}

   
   	sort(a.begin(),a.end());

   
   	for(int i = 0; i < m;i++){

   
   		int u = a[i].u,v = a[i].v,w = a[i].w;

   
   		if(fd(u)==fd(v)){

   
   			if(calc(d[u],d[v]) == 0){

   
   				cout << w << endl;

   
   				return;

   
   			}

   
   		}else{

   
   			merge(u,v);

   
   		}

   
   	}

   
   	cout << 0 <<endl;

   
   }

__EOF__

  • 本文作者: BananaWolf
  • 本文链接: https://www.cnblogs.com/bananawolf/p/18364209
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 除特殊说明外,转载请注明出处~[知识共享署名-相同方式共享 4.0 国际许可协议]
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
原创作者: bananawolf 转载于: https://www.cnblogs.com/bananawolf/p/18364209
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值