食物链 拓展域 边带权

首先我们知道并查集可以维护集合间的关系,当关系只有一种的时候是比较好维护的。但是当关系较多的时候拓展域在我看来是比边带权好想一些的,因为边带权表示多种关系的的话势必要利用每个节点到根结点的距离来做文章。
例如下题:
食物链,题目链接
边带权:
我们把所有有关系的的节点都放到一个集合内,并用每个节点到其根结点的距离来表示其与根节点的的关系,而根据每个节点与根节点的关系我们就可以得知每个节点之间的关系,设根节点为A,假设B到根节点的距离为1,并表示B被A吃。再假设C到根节点的距离为2,并表示A被C吃,那么根据题意可以得出C被B吃。
所以三个一个循环我们就可以通过(d[i]%3)来判断i与其根节点之间的关系,继而推出每个节点之间的关系。
代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int p[N];
int d[N];//d[i]代表i到该根节点的距离 
int find(int x) {
	if (x == p[x])return p[x];
	else {
		int t = find(p[x]);
		d[x] += d[p[x]];
		p[x] = t;
		return p[x];
	}
}
int main() {
	int n, k, c, x, y, res = 0;
	cin >> n >> k;
	for (int i = 1; i <= n; i++)p[i] = i;
	for (int i = 1; i <= k; i++) {
		cin >> c >> x >> y;
		if (x > n || y > n)res++;
		else {
			int dx = find(x), dy = find(y);
			if (c == 1) {
				if (dx == dy && (d[x] - d[y]) % 3)res++;
				else  if (dx != dy) {
					p[dx] = dy;
					d[dx] = d[y] - d[x];//(d[x]+d[dx]-d[y] )%3==0,
					//所以d[dx]=d[x]-d[y]
				}
			} else {
				if (dx == dy && (d[x] - d[y] - 1) % 3)res++; //(d[x]-d[y])%3有可能是-2,
				//也有可能是1而这两种情况是相同的都是x<-y。画画图就知道了。
				else if (dx != dy) {
					p[dx] = dy;
					d[dx] = d[y] + 1 - d[x] ;//同理。
				}
			}
		}

	}
	cout << res;
	return 0;
}

拓展域:
假设有N个元素构成一个集合,而每个元素之间有多种关系。假设A,B,C三个元素,B是A的食物,C是A的天敌自然A是C的食物,但是C也是B的食物。。。。就很乱,但是我们从中提取出三种关系同类,食物,和天敌。
1~n来表示自身:
n+1~2n来表示自己的食物;
2
n+1~3*n来表示自己的天敌;
那么当a与b是同类的时候,a的天敌和b的天敌是同类,a的食物和b的食物也是同类。
当a吃b的时候,a的食物和b是同类,a的天敌和b的食物是同类,b的天敌和a是同类;
类比上一个代码这个还是比较好写的,但是这个空间复杂度较高,理清楚元素之间的关系才是难点。

#include<bits/stdc++.h>
using namespace std;
const int N= 3e5+10;
int p[N];
int get(int x)
{
	if(x==p[x])return p[x];
	return p[x]=get(p[x]);
}
void merge(int x,int y)
{
	int dx=get(x);
	int dy=get(y);
	if(dx!=dy)
	{
		p[dx]=p[dy];
	}
}
int main()
{
	int n,k,res=0;
	cin>>n>>k;
	for(int i=1;i<=3*n;i++)p[i]=i;
	for(int i=1;i<=k;i++)
	{
		int c,a,b;
		cin>>c>>a>>b;
		if(a<1||a>n||b<1||b>n)res++;
		else if(a==b&&c==2) res++;
		else
		{
			if(c==1)
			{
				if(get(a)==get(b+n)||get(a+n)==get(b))res++;
				else
				{
					merge(a,b);
					merge(a+n,b+n);
					merge(a+2*n,b+2*n);
				}
			}
			else {
				if(get(a)==get(b)||get(a)==get(b+n))res++;
				else
				{
					merge(a+n,b);
					merge(a+2*n,b+n);
					merge(a,b+2*n);
				}
			}
		}
	}
	cout<<res;
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值