带权并查集:通过一个演算来推算!
poj 2492
/**
题意:
找同性恋的昆虫!
分析:
需要在并查集中动态维护
值只有0,1 的数组!可以想象成 0为母的,1为公的!
**/
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
#define LLEN 2005
int c, g;
int p[LLEN], f[LLEN];
void init() {
for(int i = 0; i <= c+5; i++) {
p[i] = i;
f[i] = 0;
}
}
int find(int x) {
if(p[x] != x) {
int k = p[x]; /**得到x原先祖先的值!**/
p[x] = find(p[x]);
f[x] = (f[x] + f[k])%2;/**每次find就动态维护f[x]的“性别”**/
}
return p[x];
}
int unionSet(int x, int y) {
int r1 = find(x);
int r2 = find(y);
if(r1 == r2) {/**不同集合的昆虫交配都可以!因为可以通过维护数组f的值来使之成立!**/
if(f[x] == f[y]) return 1;
return 0;
}
p[r2] = r1;
f[r2] =~(f[x]^f[y]) ;/**异或这两个交配的昆虫!改变其祖先值性别!使集合的交配成立!**/
return 0;
}
int main() {
int t;
scanf("%d", &t);
for(int cse = 1; cse <= t; cse++) {
int flag = 0;
scanf("%d%d", &c, &g);
init();
for(int i = 0; i < g; i++) {
int a, b;
scanf("%d%d", &a, &b);
if(!flag && unionSet(a, b)) flag = 1;
}
printf("Scenario #%d:\n", cse);
if(1 == flag) {
printf("Suspicious bugs found!\n\n");
}
else {
printf("No suspicious bugs found!\n\n");
}
}
return 0;
}
/**
1 1
0 0
只对于不同集合的并,因为若来自同一集合,那么就找到了同性恋!
所以只是对于不同集合的昆虫,若是这两只昆虫在两个不同集合中,并且他们性别一样,
那么可以通过改变其中一只昆虫的祖先值,改0为1,再在后面的find中动态维护就行了。
0 1
1 0
则可以对于两只昆虫 来自不同或相同集合!
他们的性别相同,就不需要改变祖先的值!
其实这个问题,要找的就是在一个集合中是不是0,1,0,1分布的。
即是不是相邻节点的值是不同的。或者说每加一对昆虫进来,集合能不能达到0,1的平衡!
并且考察 合并 两个同时符合这种分布的集合!
这里的任意集合的祖先值 都是0,合并之后的集合祖先也是0。
怎么想到并查集?
首先又只有公母两种状态,转换为0,1状态!
然后可以想到这是一棵状态树。
但是如果只会有一棵树,那么这题不需要并查集。
这题可能是有多颗树的合并:
那么就有可能要改变根节点(祖先)来得到一棵新的满足条件的状态树!
那么就想到了 并查集了!因为它可以压缩路径来快速的到祖先值!并且可以在
压缩路径时动态维护沿途的 0,1 值!使之满足条件!
**/
poj 1703 --这个只需在上面基础上稍修改就行!
#include <iostream>
#include<cstdio>
using namespace std;
#define MAXN 100100
int c,b;
int p[MAXN],f[MAXN];
void init() {
for(int i = 0; i <= c+5; i++) {
p[i] = i;
f[i] = 0;
}
}
int find(int x) {
if(p[x] != x) {
int k = p[x];
p[x] = find(p[x]);
f[x] = (f[x] + f[k])%2;
}
return p[x];
}
void unionSet(int x, int y) {
int r1 = find(x);
int r2 = find(y);
p[r2] = r1;
f[r2] =~(f[x]^f[y]) ;
}
int main()
{
int t;scanf("%d",&t);getchar();
while(t--)
{
scanf("%d%d",&c,&b);getchar();
init();
while(b--)
{
char op;
int x,y;
scanf("%c%d%d",&op,&x,&y);getchar();
if(op=='A')
{
int fa=find(x);
int fb=find(y);
if(fa==fb)
{
if(f[x]!=f[y]) printf("In different gangs.\n");
else printf("In the same gang.\n");
}
else
{
printf("Not sure yet.\n");
}
}
else unionSet(x,y);
}
}
return 0;
}
食物链--通过向量 推算祖先权值!
详见收藏:http://blog.csdn.net/niushuai666/article/details/6981689
/** 收藏中下面这段注释最重要!
仔细再想想,rootx-x 、x-y、y-rooty,是不是很像向量形式?于是我们可以大胆的从向量入手:
tx ty
| |
x ~ y
对于集合里的任意两个元素x,y而言,它们之间必定存在着某种联系,因为并查集中的元素均是有联系的(这点是并查集的实质,要深刻理解),否则也不会被合并到当前集合中。那么我们就把这2个元素之间的关系量转化为一个偏移量(大牛不愧为大牛!~YM)。
由上面可知:
x->y 偏移量0时 x和y同类
x->y 偏移量1时 x被y吃
x->y 偏移量2时 x吃y
有了这个假设,我们就可以在并查集中完成任意两个元素之间的关系转换了。
不妨继续假设,x的当前集合根节点rootx,y的当前集合根节点rooty,x->y的偏移值为d-1(题中给出的询问已知条件)
(1)如果rootx和rooty不相同,那么我们把rooty合并到rootx上,并且更新relation关系域的值(注意:p[i].relation表示i的根结点到i的偏移量!!!!(向量方向性一定不能搞错))
此时 rootx->rooty = rootx->x + x->y + y->rooty,这一步就是大牛独创的向量思维模式
上式进一步转化为:rootx->rooty = (relation[x]+d-1+3-relation[y])%3 = relation[rooty],(模3是保证偏移量取值始终在[0,2]间)
(2)如果rootx和rooty相同(即x和y在已经在一个集合中,不需要合并操作了,根结点相同),那么我们就验证x->y之间的偏移量是否与题中给出的d-1一致
此时 x->y = x->rootx + rootx->y
上式进一步转化为:x->y = (3-relation[x]+relation[y])%3,
若一致则为真,否则为假。
**/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 50010
struct node
{
int pre;
int relation;
};
node p[N];
int find(int x) //查找根结点
{
int temp;
if(x == p[x].pre)
return x;
temp = p[x].pre; //路径压缩
p[x].pre = find(temp);
p[x].relation = (p[x].relation + p[temp].relation) % 3; //关系域更新
return p[x].pre; //根结点
}
int main()
{
int n, k;
int ope, a, b;
int root1, root2;
int sum = 0; //假话数量
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i) //初始化
{
p[i].pre = i;
p[i].relation = 0;
}
for(int i = 1; i <= k; ++i)
{
scanf("%d%d%d", &ope, &a, &b);
if(a > n || b > n) //条件2
{
sum++;
continue;
}
if(ope == 2 && a == b) //条件3
{
sum++;
continue;
}
root1 = find(a);
root2 = find(b);
if(root1 != root2) // 合并
{
p[root2].pre = root1;
p[root2].relation = (3 + (ope - 1) +p[a].relation - p[b].relation) % 3;
}
else
{
if(ope == 1 && p[a].relation != p[b].relation)
{
sum++;
continue;
}
if(ope == 2 && ((3 - p[a].relation + p[b].relation) % 3 != ope - 1))
{
sum++;
continue;}
}
}
printf("%d\n", sum);
return 0;
}
poj1988
/**
堆盒子:每M a b操作,都把有a的那一堆放到有b的一堆的上面。
根据题意可知,这个是明显的集合操作!所以用并查集!
怎么解;
我把栈顶作为 祖先!它作为代表元保存了这个栈里有多少元素。
然后只需要维护一个p数组就ok了。
即每个元素到栈顶的距离!
这样每次查找一个元素的下面有多少盒子时,
只需要把 总数-它到顶的距离就 得到答案了。
这里要注意几个问题:
1. find操作里的更新 只有当这个元素的祖先改变时才更新这个元素的p值。
且这个值得回溯得到,并且还要减去1,因为不减就会重复算一次它到原先祖先的值!
2.为什么只有当祖先改变才去改变呢?
首先在合并的时候,就已经把这个元素到祖先的距离P已经算好了。
所以如果祖先没有改变,就不能去再find更新不然会一直重复加这个元素到祖先的值!然而这个祖先又没变!
**/
#include <iostream>
#include<cstdio>
using namespace std;
#define MAXN 40000
int f[MAXN],p[MAXN],num[MAXN];
void init()
{
for(int i=1;i<MAXN;i++)
{
f[i]=i;
p[i]=1;//每个元素到祖先的距离!
num[i]=1;//只有祖先才用这个数组
}
}
int find(int x)
{
if(x!=f[x]&&f[x]!=f[f[x]])//只有当祖先改变才去改变P!
{
int t=f[x];
f[x]=find(f[x]);
p[x]+=p[t]-1;//find完再更新,减去1是为了减去重复计算的到原祖先的距离!
}
return f[x];
}
void merge(int x,int y)
{
int fa=find(x);
int fb=find(y);
f[fb]=fa;
p[fb]=num[fa]+1;//把y的祖先到栈顶的值改变!加1就是把自己算进去的到顶的距离!
num[fa]+=num[fb];//把现在新的栈顶的NUM值维护!
//cout<<fa<<" "<<num[fa]<<" "<<endl;
}
int main()
{
int n,x,y;scanf("%d",&n);getchar();
char op;init();
while(n--)
{
scanf("%c%d",&op,&x);getchar();
if(op=='M')
{
scanf("%d",&y); getchar();
merge(x,y);
}
else
{
int t=find(x);
//cout<<"----------"<<t<<" "<<num[t]<<" "<<endl;
//cout<<"----------"<<x<<" "<<p[x]<<" "<<endl;
printf("%d\n",num[t]-p[x]);
}
}
return 0;
}
/** 测试数据
60
M 1 6
C 1
M 2 4
M 2 6
C 3
C 4
M 9 8
C 9
C 8
M 10 8
C 10
C 9
C 8
M 11 8
C 11
C 10
C 9
C 8
M 12 8
C 12
C 11
C 10
C 9
C 8
M 13 8
C 13
C 12
C 11
C 10
C 9
C 8
M 6 8
C 2
C 4
C 1
C 6
C 13
C 12
C 11
C 10
C 9
C 8
C 6
C 12
*/
还有个水题!
hdu1213
#include <iostream>
#include<cstdio>
using namespace std;
int set[1900];
int flag[1900];
int a,b;
void init()
{
for(int i=1;i<=a;i++) {set[i]=i;flag[i]=0;}
}
int find(int x)
{
return x==set[x]?x:find(set[x]);
}
void merge(int x,int y)
{
set[find(y)]=find(x);
}
int main()
{
int t;scanf("%d",&t);
while(t--)
{
int ans=0;
scanf("%d%d",&a,&b);
init();
for(int i=1;i<=b;i++)
{
int x,y;
scanf("%d%d",&x,&y);
merge(x,y);
}
for(int i=1;i<=a;i++) if(set[i]==i) ans++;
printf("%d\n",ans);
}
return 0;
}