并查集&poj1182

题目地址:poj1182

说实话,看了这篇文章之后才理解的食物链总结

代码基本也是照抄的,这里说说理解。

显然是用并查集,但这里不是简单地使用并查集,好像大家都称之为带权并查集。


首先看看说法错误,有3种:

1) 当前的话与前面的某些真的话冲突,就是假话; 
2) 当前的话中X或Y比N大,就是假话; 
3) 当前的话表示X吃X,就是假话。


假如X,Y不是第2)第3)种错误的关系,则有两种情况:

①X,Y由此前给出的说法可以判断他们的关系。

②X,Y不能由此前给出的说法判断答案判断他们的关系。


显然若X,Y有关系,那么用集合的思想就是它们属于同一个集合。没有关系就是不属于同一个集合。

第②种情况中,若不能由此前的关系判断他们的关系,那么这句话肯定是对的(题意),并且应该作为一种新的关系加入到集合中或者创建一个新的集合。


至于第①种情况,因为可以由之前的说法判断他们的关系,因此他们可以看作是存在直接或间接的关系的,属于同一个集合。

那么如何判断一个集合中两者的关系呢?使用大神所说的向量思维


假设有x,y和他们各自的父亲tx,ty。其中知道关系

tx                ty

|           |

x   -----    y

即tx(父)->x(子),ty->y,x->y的关系,并且将这些关系数量化 rank = 0/1/2,分别表示父子同类,父吃子,子吃父。

称这些数为偏移量。如tx->x则称tx到x的偏移量(x->tx的偏移量就是tx->x的偏移量的相反数)

因为tx到ty存在一条路径,那么必然可以通过这些数量关系求出tx到ty直接偏移量。(别问我为什么知道)

     tx  -----  ty

     |             |

     x   -----  y

       tx->ty =tx->x +  x->y  +  y->ty 

             tx->ty = tx->x +  x->y  -   ty->y 


好的上面一大段文字说了什么呢?大概就是说X,Y的关系可以通过其他已知的而且和X,Y有关的关系得出 *A

设delta[i]为i和i的父亲,设为fa[i]的关系。即tx->x = delta[x]...

由上面填充了颜色的公式就可以求出他们的关系了

delta[ty] = delta[x] + delta[y] -delta[y];

由于运算过程中可能会导致最后的计算结果超出0~2的范围,可以对其加3再取模(还记得数据结构中循环队列是怎样解决假溢出的问题吗?自行百度。。)

delta[ty] = ( delta[x] + delta[y] -delta[y] +3 )%3;


因此合并两个集合的大概的操作就有

unio(int x,int y){
    int tx = find(x);
    int ty = find(y);

    fa[ty] = tx;
    delta[ty] = ( delta[x]+delta[y]-delta[y]+3 )%3;
}


其中利用*A性质还可以进行路径压缩

      ffx  --\                     ffx

      |        \                     |    

     fx        |      -->          | \

      |        /                    fx  x

     x  __/


  ffx->x = ffx->fx +fx->x;


int find(int x){
    if(x==fa[x])    return x;
    int tx = find(fa[x]);
    delta[x] = (delta[x]+delta[fa[x]])%3;
    return fa[x] = tx;
}


完整代码:

#include<cstdio>
const int maxn = 50000+10;
int fa[maxn],r[maxn];	//fa存父节点,r存该点到父节点的关系
int N;

void init(){
	for(int i=1;i<=N;i++){
		fa[i] = i;
		r[i] = 0;
	}
}

int find(int x){
	if(x==fa[x])	return x;
	int tx = find(fa[x]);
	r[x] = (r[x]+r[fa[x]])%3;	//使用向量思维进行路径压缩
	return fa[x] = tx;
}

//合并的同时判断是否正确
int unio(int x,int y,int type) {
	if(x>N||y>N)	return 1;	//返回1就是假话了
	if(x==y&&type==1)	return 1;	//xy是同类但是互相吃就是错误

	int tx = find(x);
	int ty = find(y);	

	if(tx==ty){	//本来x,y就是有关系的
		if((r[y]-r[x]+3)%3!=type)	return 1;
		else return 0;
	}

	fa[ty] = tx;
	r[ty] = (r[x]+type-r[y]+3)%3;
	return 0;

}


int main(){
	int K;
	int d,x,y;
	int wrong = 0;



	scanf("%d%d",&N,&K);
	init();

	for(int i=0;i<K;i++){
		//cin>>d>>x>>y;
		scanf("%d%d%d",&d,&x,&y);
		if( unio(x,y,d-1) )
			wrong++;
	}

	
	printf("%d\n",wrong);
	return 0;

}










猜你喜欢

转载自blog.csdn.net/l_apple8/article/details/50710104