POJ1182 BIT1063 食物链

题:

动物王国中有三类动物 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 ),输出假话的总数。

输入:

输入包括多组数据,

每组数据第一行是两个整数 N 和 K ,以一个空格分隔。 
以下 K 行每行是三个正整数 D , X , Y ,两数之间用一个空格隔开,其中 D 表示说法的种类。 
若 D=1 ,则表示 X 和 Y 是同类。 
若 D=2 ,则表示 X 吃 Y 。

输入以EOF结尾 

输出:

对于每组数据,输出一行包含一个整数,表示假话的数目。

解法:

并查集,我是一边写代码一边写这篇博客的

并查集的实现

parent[i]表示i的父节点的编号

relation[i]表示i与父节点的关系

0 表示该节点和父节点是同类

-1 表示该节点吃父节点

1 表示该节点被父节点吃

以上三个值是我自己规定的

初始化为

for(int i=1;i<=n;i++)
{
parent[i]=i;//初始时父亲就是自己
relation[i]=0;//与自己的关系是同类
}

对于sample

数据做分析

110 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5
初始时,

编号     父亲    与父亲的关系

1            1               0  

2            2               0

3            3               0

5            5               0

101       101           0

输入第一句话

1 101 1

说 101 和 1是同类

找到101和1的根节点就是101和1

他俩的关系是同类,于是在并查集中的结构为


其中的0表示101和1是同类

第二句话

2 1 2

表明1吃2

找到1和2的根节点分别为1和2

在并查集中的结构为


第三句话:

2 2 3

表明2吃3

而2和3的根节点分别为1和3

由于2到根节点的路径的权值为1,为正奇数,说明2被根节点吃,而2又吃3,故3吃1

那么1和3的关系为1被3吃,画图为


对此图,有

101到根节点的权为1        101被根节点吃    等效为1

2到根节点的权为2             2吃根节点             等效为-1

1到根节点的权为1             1被根节点吃        等效为0

对此进行分析

设节点i到其根节点的权为m   

m=0*k1+1*k2+(-1)*k3

很明显在路径中间的0并不影响其与根节点的关系

由上面样例可以知道两个1等效为一个-1,同样两个-1也等效为一个1(这点随便画个图就知道)

三个1或-1就可以等效为0了(自己画图验证)

1和-1又可以等效为0(自己画图验证)

所以m%3完全可以代表该节点与根节点的关系,m%3=0等效为0,m%3=2等效为-1,m%3=1等效为1

在并查集的合并操作中,我们要合并a和b,假设ra和rb为a和b的根节点

ma与mb为带权路径值,代表着a和b分别与根节点的关系

对所有情况作分析

a与b的关系          a与ra的关系    b与rb的关系                 ra与rb的关系      

a吃b   -1                      1(a被ra吃)  1(b被rb吃)              ra吃rb                  -1

a吃b  -1                    -1(a吃ra)     -1(b吃rb )               ra吃rb                    -1

a吃b   -1                    0                       0                                   ra吃rb                   -1

a吃b   -1                    1                        -1                                 ra与rb同类            0

a吃b    -1                    -1                     0                                   ra与rb同类           0

a吃b     -1                     0                      1                                 ra与rb是同类         0

a吃b      -1                  -1                      1                                  rb吃ra                    1

a吃b      -1                  1                         0                                rb吃ra                     1

a吃b       -1                  0                       -1                                想通了

以上的9个分析真是麻烦,作一次推理

a与b的关系为mab

a与rb的关系为mab+mb

a与ra的关系为ma                          

那么ra与rb的关系必为mab+mb-ma,用以上九个例子验证通过

                           


接下来我去码代码了,AC后贴代码

AC了,贴代码

在BIT要AC,你要写while(scanf("%d %d",&n,&k))

在POJ要AC,你要写scanf("%d %d",&n,&k);

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int parent[50010];
int relation[50010];
int counter;
struct T
{
	int num;//根节点的编号
	int quan;//到根节点的权
};
T root(int i)
{
	T ans;
	if(parent[i]==i)
	{
		ans.num=i;
		ans.quan=0;
		return ans;
	}
	T tt=root(parent[i]);
	parent[i]=tt.num;//路径压缩
	relation[i]+=tt.quan;
	relation[i]%=3;
	while(relation[i]<0)
	{
		relation[i]+=3;
	}
	if(relation[i]==2)
	{
		relation[i]=-1;
	}
	ans.num=parent[i];
	ans.quan=relation[i];
	return ans;
}
void Merge(int mab,int a,int b)
{
	T tempa=root(a);
	T tempb=root(b);
	int ra=tempa.num;
	int rb=tempb.num;
	int ma=tempa.quan;
	int mb=tempb.quan;
	parent[ra]=rb;
	int tt=mab+mb-ma;
	tt%=3;
	while(tt<0)
	{
		tt+=3;
	}
	if(tt==2)
	{
		tt=-1;
	}
	if(ra==rb&&tt!=0)//冲突了
	{
		counter++;
		return;
	}
	if(ra!=rb)
	{
		relation[ra]=tt;
	}
}
int main()
{
	int n,k;
	while(scanf("%d %d",&n,&k))
	{
		for(int i=1;i<=n;i++)
		{
			parent[i]=i;
			relation[i]=0;
		}
		counter=0;//假话的数目
		while(k--)
		{
			int D,X,Y;
			scanf("%d %d %d",&D,&X,&Y);
			if(X>n||Y>n)//满足第二条规则,假话
			{
				counter++;
				continue;
			}
			if(D==2&&X==Y)//满足第三条规则,假话
			{
				counter++;
				continue;
			}
			if(D==1&&X==Y)
			{
				continue;
			}
			if(D==1)
			{
				D=0;//X与Y是同类
			}
			if(D==2)
			{
				D=-1;//X吃Y
			}
			Merge(D,X,Y);
		}
		printf("%d\n",counter);
	}
	return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值