POJ-1182-食物链

POJ-1182-食物链

传送门

这道题是并查集的经典题型。
难度高于模板裸题。但是又在可控的范围内。

中文题就不描述啦~

结构体中记录结点信息:p[i].fa记录父结点,p[i].rela记录与父结点的关系。
我们路径压缩的时候更新父结点为根结点。这点是很显然的。

首先进行归定:
p[x].rela = 0;代表与父结点是同类关系。
p[x].rela = 1,代表与父结点的关系:父结点吃x
p[x].rela = 2;代表与父结点的关系:x吃父结点

特判:
1.当输入的a, b任意一个大于给定n的时候就让sum++.(谎话++)
2.当op = 2 && a == b的时候。不允许存在a吃自己的情况。sum++

我们再方位到a, b的根结点。
判断根结点是否相同。
不相同进行mix合并。
相同的话:
特判情况:如果op == 1;属于相同种类时,他们与根结点的关系不一样。sum++
还有一种特判情况:op == 2是被吃的情况下。需要用式子来判断。

这里关键是三个式子需要注意:

这里逐一解释:
find()部分:
p[x].rela = (p[t].rela + p[x].rela) % 3;
t代表x原来的父结点
这个式子是把当前结点的关系更新
因为父结点已经路径压缩为根结点了。
这里枚举一两个式子可以得到上面的关系式:
比如:
当p[x].rela = 1;x与原父结点t是:t吃x
p[t].rela = 0;原父结点t和根结点是:同类关系。
所以x与根结点的关系:根结点吃x;更新为1.
(找数字规律找出来的。没有什么方法。。emmm。就是枚举找规律。。)
通过枚举我们可以总结出是两者的关系相加之后模3的关系(为啥是模3.因为按照先开始的规定就只有0,1,2这三个数。)

当判断两个根结点不相同,需要进行合并的时候:
我们把b的根结点root2更新为a的根结点root1.
那么root2的rela也需要进行更新:
这个地方的式子也是通过枚举找规律发现的:
p[root2].rela = (p[a].rela - p[b].rela + (op == 1 ? 0 : 1) + 3) % 3;

假设op = 1的情况下:a和b是同类情况:
p[a].rela = 0, p[b].rela = 0;
我们可以得到p[root2].rela = 0;
p[a].rela = 0, p[b].rela = 1;
我们可以得到p[root2].rela = 2;
p[a].rela = 0, p[b].rela = 2;
我们可以得到p[root2].rela = 1;
p[a].rela = 1, p[b].rela = 0;
我们可以得到p[root2].rela = 1;
…模拟完op = 1之后模拟op = 2的情况:
p[a].rela = 0, p[b].rela = 0;
我们可以得到p[root2].rela = 1;

我们可以发现op=1的时候是a的rela-b的rela然后模3得到的。

我们还可以发现op = 2的情况是在op = 1的情况上+1模3得到的。
所以式子就出来了

还有一个式子是特判情况:op == 2是被吃的情况下:
判断不符合已经构造好的关系的情况:
方法也是枚举:(枚举符合要求的情况)
前提条件:a和b同根。a吃b
当p[a].rela = 1时:p[b].rela应该等于2
…这样枚举下来;
我们可以得到b的rela-a的rela模3等于1需要成立。如果不成立说明矛盾:
式子:
p[b].rela - p[a].rela + 3) % 3

这个解决完了就很简单了

代码部分:

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 5e4 + 10;

struct node
{
	int fa;
	int rela;
}p[N];
int n, k;
int op;
int a, b;
int sum;

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

int find(int x)
{
	if (x != p[x].fa)
	{
		int t = p[x].fa;
		p[x].fa = find(p[x].fa);
		p[x].rela = (p[t].rela + p[x].rela) % 3;
		return p[x].fa;
	}
	return p[x].fa;
}

int main()
{
	cin >> n >> k;
	init();
	for (int i = 1; i <= k; i++)
	{
		scanf ("%d%d%d", &op, &a, &b);
		if (a > n || b > n)
		{
			sum++;
			continue;
		}
		if (op == 2 && a == b)
		{
			sum++;
			continue;
		}
		int root1 = find(a);
		int root2 = find(b);
		if (root1 != root2)
		{
			p[root2].fa = root1;
			p[root2].rela = (p[a].rela - p[b].rela + (op == 1 ? 0 : 1) + 3) % 3;
		}
		else
		{
			if (op == 1 && p[a].rela != p[b].rela)
			{
				sum++;
			}
			else if (op == 2 && (p[b].rela - p[a].rela + 3) % 3 != 1)
			{
				sum++;
			}
		}
	}
	cout << sum << endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

娃娃酱斯密酱

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

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

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

打赏作者

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

抵扣说明:

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

余额充值