0x41 并查集

目录

并查集模板

poj1456

题目链接

题目

题解

代码

边带权并查集例题1

题目链接

题意

题解

代码

边带权并查集例题2

题目链接

题目

题解

代码

扩展域并查集例1

题目链接

题目

题解

代码

扩展域并查集例2

题目链接

题目

题解

代码



并查集模板

首先是初始化,然后是查找和合并操作。

void init() {
	for(int i=1;i<=n;i++)	
		fa[i] = i;
}

int get(int x) {
	if(x==fa[x])	return x;
	return fa[x] = get(fa[x]);	
}

void merge(int x,int y) {
	fa[get(x)] = get(y);		
}

poj1456

题目链接

  http://poj.org/problem?id=1456  

题目

  有n个商品,每个商品有利润p和过期时间d,每天只能卖一个商品。过期的商品不能再卖,问如何安排每天卖的商品。

题解

  上次用 优先队列+贪心 解决了这一道问题:https://blog.csdn.net/zxwsbg/article/details/82717757

  这次换一种贪心思路,我们直接按照每个商品的利润从大到小排序,建立一个关于d的并查集。

  如果在当前商品在d天后过期,就找从d往前数第一个空闲的位置( fa[d] ), 如果fa[d]>0,那么就合并(fa[d],fa[d]-1),这样如果下一次又有一个点在d天后过期,它找到的就是fa[fa[d]-1]了。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;

#define INIT(x) memset(x,0,sizeof(x))
#define eps 1e-8
#define next next_
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int inf = 0x3f3f3f3f;
const int maxn = 20005;
const int N = 105;

inline void read(int &x) {
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

int n,fa[maxn];
struct node{
	int p,d;
	bool operator<(const node&b)const{
		return p>b.p;
	}
}a[maxn];

void init() {
	for(int i=0;i<=10000;i++)	
		fa[i]=i;
}

int get(int x) {
	if(x==fa[x])	return x;
	return fa[x]=get(fa[x]);	
}

void merge(int x,int y) {
	fa[get(x)] = get(y);	
}

int main()
{
	while(cin>>n) {
		ll ans = 0;
		for(int i=1;i<=n;i++)	read(a[i].p),read(a[i].d);
		sort(a+1,a+n+1);	
		init();
		for(int i=1;i<=n;i++) {
			int temp = get(a[i].d);
			if(temp>0) {
				ans += a[i].p;
				merge(temp,temp-1);
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}



边带权并查集例题1

题目链接

  银河英雄传说

题意

  一个划分为n列的星际战场,各列编号为1,2,3……n。有n艘战舰,也依次编号为1,2,3……n,其中第i号战舰处于第i列。

  有m条指令,每个指令为下列两种之一:

  1. M i j ,将第i列的战舰保持原有顺序,接到第j列尾部;
  2. C i j ,询问第i列和第j列战船是否在同一列中,如果是,输出它们之间隔了多少战船,否则输出-1。

题解

  在原有的基础上,我们新建一个数组d,d[x]记录战舰 x 与 fa[x] 之间的边的权值。在路径压缩把x直接指向它的祖先时,把d[x]更新为从x到它祖先路径上所有边的权值的和。 

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;

#define INIT(x) memset(x,0,sizeof(x))
#define eps 1e-8
#define next next_
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
const int N = 105;

inline void read(int &x) {
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

int t,d[maxn]; //d数组记录的是从d[x]到d[fa[x]]的距离 
string s;
int a[maxn],fa[maxn],size[maxn];

void init() {
	for(int i=0;i<=30005;i++)	fa[i] = i, size[i] = 1; //size记录的是该根节点下有多少个子节点 
}

int get(int x) {
	if(x==fa[x])	return x;
	int root = get(fa[x]);  //此时fa[fa[x]]已经维护好了,root记录的就是这个值 
	d[x] += d[fa[x]];  //把x叠加上去 
	return fa[x] = root;  //再把x的爸爸记录成fa[fa[x]] 
}

void merge(int x,int y) {
	x = get(x), y = get(y);
	fa[x] = y;
	d[x] = size[y];
	size[y] += size[x];
}

int query(int l,int r) {
	if(get(l)!=get(r))	return -1;
	return abs(d[r]-d[l])-1;
}

int main()
{
	cin >> t;
	init();
	while(t--) {
		cin>>s;
		int l,r;
		read(l),read(r);
		if(s=="M")	merge(l,r);
		else	cout<<query(l,r)<<endl; 
	}
	return 0;
}



边带权并查集例题2

题目链接

  http://poj.org/problem?id=1733

题目

  有一个长度已知的01串,给出[l,r]这个区间中的1是奇数个还是偶数个,给出一系列语句,问前几个是正确的。

题解

  首先,s[l~r]有偶数个1,表示sum[l-1]和sum[r]的奇偶性相同,奇数个1同理。

  然后我们观察数据范围,可以先把所有的询问离散化。

  边权d[x]=0,表示x与fa[x]的奇偶性相同,反之不同。

  1. 如果x和y在同一个根节点下,那么在get(x),get(y)执行完成后,x和y的奇偶性关系就是d[x]^d[y],如果这个值和ans不同,就找到了违反规则的点了;
  2. 如果两个不在一个根节点下,那么就寻找两个的树根,记为p和q,然后将p接到q上去:fa[p] = q;然后x到y的路径就变成了x->p->q->y,ans=d[x]^d[y]^d[p]。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;

#define INIT(x) memset(x,0,sizeof(x))
#define eps 1e-8
#define next next_
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
const int N = 105;

inline void read(int &x) {
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

int n,m,a[maxn*10],fa[maxn*10],d[maxn];
struct node{
	int l,r,ans;
}q[maxn];

void discrete() {
	int t = 0;
	for(int i=0;i<m;i++) {
		a[t++] = q[i].l-1;
		a[t++] = q[i].r;
	}	
	sort(a,a+t);
	n = unique(a,a+t)-a;
}

void init() {
	for(int i=0;i<=2*n;i++)	fa[i] = i;	
}

int get(int x) {
	if(x==fa[x])	return x;
	int root = get(fa[x]);
	d[x] ^= d[fa[x]];
	return fa[x] = root;
} 

void merge(int x,int y) {
	fa[get(x)] = get(y);
}

int main()
{
	cin>>n>>m;
	for(int i=0;i<m;i++)	{
		read(q[i].l),read(q[i].r);
		string s;
		cin>>s;
		if(s=="even")	q[i].ans = 0;
		else	q[i].ans = 1;
	}
	discrete();
	init();
	for(int i=0;i<m;i++) {
		int x = lower_bound(a,a+n,q[i].l-1)-a;
		int y = lower_bound(a,a+n,q[i].r)-a;
		int fa_x = get(x), fa_y = get(y);
		if(fa_x==fa_y) {
			if(d[x]^d[y]!=q[i].ans) {
				cout<<i<<endl;
				return 0;
			}
		}else {
			fa[fa_x] = fa_y;
			d[fa_x] = d[x] ^ d[y] ^ q[i].ans;
		}
	}
	cout<<m<<endl;
	return 0;
}

扩展域并查集例1

题目链接

  http://poj.org/problem?id=1733

题目

  有一个长度已知的01串,给出[l,r]这个区间中的1是奇数个还是偶数个,给出一系列语句,问前几个是正确的。

题解

  刚才用了“边带权”的并查集,这一次用扩展域并查集。

  将x分为两个节点x_odd和x_even,y同理,如果x和y奇偶性相同的话,则合并x_odd与y_odd,x_even和y_even,依次类推。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;

#define INIT(x) memset(x,0,sizeof(x))
#define eps 1e-8
#define next next_
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
const int N = 105;

inline void read(int &x) {
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}


int n,m,a[maxn*10],fa[maxn*10];
struct node{
	int l,r,ans;
}q[maxn];

void discrete() {
	int t = 0;
	for(int i=0;i<m;i++) {
		a[t++] = q[i].l-1;
		a[t++] = q[i].r;
	}	
	sort(a,a+t);
	n = unique(a,a+t)-a;
}

void init() {
	for(int i=0;i<=2*n;i++)	fa[i] = i;	
}

int get(int x) {
	if(x==fa[x])	return x;
	return fa[x] = get(fa[x]);
} 

void merge(int x,int y) {
	fa[get(x)] = get(y);
}

int main()
{
	cin>>n>>m;
	for(int i=0;i<m;i++)	{
		read(q[i].l),read(q[i].r);
		string s;
		cin>>s;
		if(s=="even")	q[i].ans = 0;
		else	q[i].ans = 1;
	}
	discrete();
	init();
	for(int i=0;i<m;i++) {
		int x = lower_bound(a,a+n,q[i].l-1)-a;
		int y = lower_bound(a,a+n,q[i].r)-a;
		int x_odd = x, x_even = x+n;
		int y_odd = y, y_even = y+n;
		if(!q[i].ans) {
			if(get(x_odd)==get(y_even)) {
				cout<<i<<endl;
				return 0;
			}
			merge(x_odd,y_odd);
			merge(x_even,y_even);
		}else {
			if(get(x_odd)==get(y_odd)) {
				cout<<i<<endl;
				return 0;
			}
			merge(x_odd,y_even);
			merge(x_even,y_odd);
		}
	}
	cout<<m<<endl;
	return 0;
}


扩展域并查集例2

题目链接

  http://poj.org/problem?id=1182

题目

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。 
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 
有人用两种说法对这N个动物所构成的食物链关系进行描述: 
第一种说法是”1 X Y”,表示X和Y是同类。 
第二种说法是”2 X Y”,表示X吃Y。 
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。 
1) 当前的话与前面的某些真的话冲突,就是假话; 
2) 当前的话中X或Y比N大,就是假话; 
3) 当前的话表示X吃X,就是假话。 
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。

题解

  把每个动物拆分成三个节点:x_self,x_enemy,x_eat,如果x与y是同类的话,就合并x_eat和y_self,x_self和y_eat;x吃y也是同理。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;

#define INIT(x) memset(x,0,sizeof(x))
#define eps 1e-8
#define next next_
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
const int N = 105;

inline void read(int &x) {
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}


int n,m,a[maxn*10],fa[maxn*10];
struct node{
	int l,r,ans;
}q[maxn];

void discrete() {
	int t = 0;
	for(int i=0;i<m;i++) {
		a[t++] = q[i].l-1;
		a[t++] = q[i].r;
	}	
	sort(a,a+t);
	n = unique(a,a+t)-a;
}

void init() {
	for(int i=0;i<=2*n;i++)	fa[i] = i;	
}

int get(int x) {
	if(x==fa[x])	return x;
	return fa[x] = get(fa[x]);
} 

void merge(int x,int y) {
	fa[get(x)] = get(y);
}

int main()
{
	cin>>n>>m;
	for(int i=0;i<m;i++)	{
		read(q[i].l),read(q[i].r);
		string s;
		cin>>s;
		if(s=="even")	q[i].ans = 0;
		else	q[i].ans = 1;
	}
	discrete();
	init();
	for(int i=0;i<m;i++) {
		int x = lower_bound(a,a+n,q[i].l-1)-a;
		int y = lower_bound(a,a+n,q[i].r)-a;
		int x_odd = x, x_even = x+n;
		int y_odd = y, y_even = y+n;
		if(!q[i].ans) {
			if(get(x_odd)==get(y_even)) {
				cout<<i<<endl;
				return 0;
			}
			merge(x_odd,y_odd);
			merge(x_even,y_even);
		}else {
			if(get(x_odd)==get(y_odd)) {
				cout<<i<<endl;
				return 0;
			}
			merge(x_odd,y_even);
			merge(x_even,y_odd);
		}
	}
	cout<<m<<endl;
	return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

总想玩世不恭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值