poj 食物链 数据结构 并查集

Description

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

Input

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

Output

只有一个整数,表示假话的数目。

Sample Input

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

Sample Output

3

Source

Noi 01

解法一 用带权值的并查集
本题就是需要我们判断给定的两点是否满足某种关系。同一颗树里面的任意两节点间的关系我们是想知道的。
1。。根据向量如果已知A与b的关系(共只有三种关系),b与c的关系,那定会推出A与c的关系,可以在纸上画画,更清楚。据此,就能求得每个节点到其根节点的关系。
2。。 对于现在不在同一颗树中元素,合并前需知道两个子树的根节点的关系。这在后面的推倒
上面两个问题是解本题的核心!!!

之前并查集是处理的一类的元素操作,而该题元素之间有关系(同类,吃,被吃),因此对于每个元素除了需要保存每个元素的父亲节点,还需保存于父亲节点的关系。
之前的树里面代表任意两个数在一个集合,而此问题代表任意两个数的关系的以确认。

在进行树与树间合并时,是合并已经确定关系的元素,如现有根节点A的子树 、根节点B的子树如知道节点A 与节点b的关系就可合并。根据我们保存的信息我们可以推出任意节点与其根节点的关系,具体细节见代码注释。

补充:我看别人都说向量解答,刚开始有点懵,然后发现有点像相对位置,可以相加(特殊的规则),因此本问题可以扩展多个不同种类的动物。


#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
int fa[50010];
int r[50010];
int ct;
void init(int n){
    int i;
    for(i=1;i<=n;i++)
    {
        fa[i] = i;
        r[i] = 0;
    }
}
int find(int x)
{
    if(x != fa[x]){
        int tmp = fa[x];   //注意,x的父亲节点经过递归会改变,因此需临时保留。
        fa[x] = find(fa[x]);  
        r[x] = (r[tmp] + r[x])%3;  //这了首先明白默认规则 r【i】的值为0,代表 i节点与根节点同类
                                                            // r【i】的值为2,代表 i节点被根节点吃
                                                            //  r【i】的值为1,代表 i节点吃根节点
                                                            //关系是通过列出所有情况所找出来的,情况不多,就十来种 (枚举所有x取0,1,2,fa【x】(x父亲)取0,1,2的情况)就能看出x与其祖父的关系)
                                                            //而并查集的UNION(合并) 是把能确定两点关系的节点保存在一颗树中
                                                            //如果我们知道任意两点的关系,就能把他们放在同一颗树中(因为确定x--rx关系
                                                            //rx--ry关系 x--y关系)[可以在纸上画画更清晰]                                    
    }
    return fa[x];
}
bool Union(int d,int x,int y)
{
    int rx = find(x);
    int ry = find(y);
    //printf("x=%d %d %d %d  ry=%d %d %d %d\n",x,rx,r[x],fa[x],y,ry,r[y],fa[y]);
    if(rx == ry){      //已经知道x、y间的关系,直接判断,去掉不合法
        if( d==1 && r[x] != r[y])
        return 1;
        if(d==2)
        {
            /*if( (r[x] == 0 &&r[y]!=2) || (r[x] == 1 &&r[y]!=0)||
            (r[x] == 2 &&r[y]!=1))
            return 1;*/
            if(r[x] == 2 && r[y] != 0) return 1;
            if(r[x] == 1 && r[y] != 2) return 1;
            if(r[x] == 0 && r[y] != 1) return 1;
        }
        return 0;
    }
    fa[rx] = ry;
    r[rx] = (d-1 + r[y] - r[x] +3)%3; // ( ((d-1) +r[y]) + (3-r[x]) )%3
    //把x所在的子树连接到y的根节点上 (只需改x的根节点rx与y的根节点ry的关系)
     //  公式的推倒,是难点!!! 首先我们已知x与y的关系(就是d的值),x与x的根节点的关系(即为r[])
     // 首先可以通过r【i】,r【fa】的值知道i与fa【fa【i】】的关系   这里我们可以导出x与ry的关系(1)
     // 然后还需推倒一下:通过r【i】的值,导出父亲对孩子的关系,    这里我们可以导出rx与x的关系(2)
     //最后 在利用一下 1 的性质知道 rx与ry的关系  这里我们可以导出rx与ry的关系
    return 0;
       
}
int main()
{
    int i,j,k,n,m,d,x,y,tmp;
    ct=0;
    scanf("%d%d",&n,&m);
    init(n);
    for(i=0;i<m;i++){
        scanf("%d%d%d",&d,&x,&y);
        if(d==2&&x==y || x>n || y>n)
        {
            //printf("i = %d \n",i);
            ct++;continue;    
        }
        tmp = Union(d,x,y);    
        //if(tmp){
            //printf("i = %d \n",i);
        //}
        ct += tmp;
    }
    printf("%d\n",ct);
    return 0;
}

法2
巧妙地解法,超出常规的思维。
把本题变种成一般的并查集。
同样
本题就是需要我们判断给定的两点是否满足某种关系。同一颗树里面的任意两节点间的关系我们是想知道的。

该开始第一次看本题在《挑战程序设计》中没懂。。
此次了解了。
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
int fa[3*50010];   
int r[3*50010];
int find(int x){
	if(x != fa[x]){
		fa[x] = find(fa[x]);
	}
	return fa[x];
}

int same(int x,int y){
	if(find(x)  == find(y))
	return 1;
	return 0;
}
void merge(int x,int y){            //一棵树里面的元素a,b,则代表有通过a能找到与b的关系,或不能通过a找到b的关系,具体什么关系还看a与b的差值(几倍n)
                                    //小一倍数代表同类,小两倍代表a可吃b,小三倍代表被b吃(此时的a是小于n)

 x=find(x);y=find(y);
	if(r[x] < r[y]){
		fa[x] = y;
	}
	else {
		if(r[x] == r[y]){
			r[x] ++ ;
		}
		fa[y] = x;
	}
} 
int main()
{
	int i,j,k,n,m;
	int d,x,y; 
	int count  = 0;
	scanf("%d%d",&n,&k);
	for(i=1;i<=3*n;i++) {  //把每个元素变成3个,设第一个为x,则第二个为x+n,第三个 x+2n
                    fa[i] = i;r[i] = 0;	
	}
	for(i=0;i<k;i++){		
		scanf("%d%d%d",&d,&x,&y);
		if(x>n || y>n){
			count++;
			continue;
		}
		if(d==1){
			if(same(x,y+n)||same(x,y+2*n)) //已知x y不是同一类 ,排除的情况
			{
				count++;
				continue;
			}
			merge(x,y);
			merge(x+n,y+n);
			merge(x+2*n,y+2*n);
		}	
		else {
			if(same(x,y)||same(x,y+2*n))  //已知x 不能吃 y,,排除的情况
			{
				count++;
				continue;
			}
			merge(x , y+n);
			merge(x+n , y+2*n);
			merge(x+2*n , y);
		}	
		
	}
	printf("%d\n",count);
	return 0;
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值