凡人修c传(九)带权的并查集(食物链)

        今天在学长的帮助下,总算入门了一点带权的并查集,个人感觉的话,所谓并查集带权,就是在并查集中构建一个向量的框架。一般的并查集板子题,可以说是一个无相图,是不用考虑什么方向的,但,带权的并查集没那么简单。应该说,pre[x]=y本身就有方向的意义,可以理解为pre[x]->y,但是入门的板子并查集无序考虑。在这层意义上,我们就可以附加向量的意义了(高中数学yyds!!!)。比如pre[x]=y,也就是x->y,但是向量的意义不只有方向嘞,还得有它本身的值,那么我们就可以再开一个数组来记录它的值,例如qz[x]=3,拿就可以描述一个x->y,且值为3的向量了。嗯嗯!?你说这跟带权并查集有什么关系?一般来说,并查集就像很多树,我们的每一个数都要指向它的树根。比如pre[x]=y,pre[y]=z,那么最终我们呈现出来的x的指向就是pre[x]=z,再加上我们的权值,也就是为这个向量加上一个值,例如qz[x]=3,qz[y]=4,那么用高中数学的知识我们可知,向量x->z就是pre[x]=z,其值为qz[x]+qz[y]=3+4=7嘞。

实践是检验真理的唯一标准:240. 食物链 - AcWing题库

我们可以把两者之间的关系成0(同种动物),1(a吃b),2(a被b吃)。让他们以树根为标准,同一颗树,树上的每一个树都表示的是该节点和树根的关系(用向量的值来表示),比如k与l是同一个树上的不同节点,那么例如k与树根的关系是0,pre[k]=end,qz[k]=0,而再假设l与树根的关系是1,pre[l]=end,qz[l]=1,那么那么我们不难推出二者之间的关系。

那如果是两个不同的树的节点相交了怎么办嘞,那就把他们合并成一颗树。

凡人修c传(九)带权的并查集(食物链)_第1张图片

 举个例子,一棵树是3,4,5(树根为5):动物4和5的关系是1(4吃5),而3和5的关系是2(3被5吃)。而另一棵树是7,8,9(树根为9):动物7和9的关系是2(7被9吃),而8和9的关系是1(8吃9)。

此时,规定动物4和7的关系是0(同种动物)。那么我们可以把两个树的节点相连,变成一棵树,也就是将5指向9,那么5和9之间的关系呢?答案是:高中数学

凡人修c传(九)带权的并查集(食物链)_第2张图片

 向量嘛,5和9的关系就是向量5->9嘛。这里不做详细计算,高中数学嘛。

这样两个树就合并成了一颗树,而9也成为了树的树根。那3,4,5的那棵树之前的节点也都向9看齐,全员都+1再%3。

上代码:

#include
#include
#include
#include
#include
#include
#include
typedef long long ll;
using namespace std;
ll pre[200005];
ll c[200005];
ll find(ll x){
	if(pre[x]==x)return pre[x];
	else{
		int t=pre[x];
		pre[x]=find(pre[x]);
		c[x]=(c[x]+c[t])%3;
		return pre[x];
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie();
	cout.tie();
	int n,m;
	cin>>n>>m;
	ll ans=0;
	for(int i=1;i<=n;i++){
		pre[i]=i;
	}
	while(m--){
		int id,a,b;
		cin>>id>>a>>b;
		if(a>n||b>n){
			ans++;
			continue;
		}
		if(id==2&&a==b){
			ans++;
			continue;
		}
		if(id==1){
			int xx=find(a);
			int yy=find(b);
			if(xx==yy){
				if(c[a]!=c[b]){
					ans++;
				}
			}
			else{
				pre[xx]=yy;
				c[xx]=(c[b]-c[a]+3)%3;
			}
		}
		if(id==2){
			int xx=find(a);
			int yy=find(b);
			if(xx==yy){
				if((c[a]-c[b]+3)%3!=1){
					ans++;
				}
			}
			else{
				pre[xx]=yy;
				c[xx]=(c[b]-c[a]+4)%3;
			}
		}
	}
	cout<

你可能感兴趣的:(算法,数据结构,c++)