POJ 1182食物链(分集合以及加权两种解法) 种类并查集的经典

题目链接:http://icpc.njust.edu.cn/Problem/Pku/1182/

题意:给出动物之间的关系,有几种询问方式,问是真话还是假话。

定义三种偏移关系:

x->y 偏移量0xy同类

x->y 偏移量1xy

x->y 偏移量2xy

定义 rela[x]=rx->x;

x,y不在同一个集合中,

rx->ry=rx->x + x->y + y->ry=(rx->x)+(x->y)-(ry->y)可得

rx->ry=rela[ry]=rela[x]+rela[y]+(x->y);

由于关系仅限0,1,2,三种,对上面关系加三后取模才是正确的关系;

如果x,y在同一个集合中,

x->y=x->rx+rx->x;

所以x->y=(3-rela[x]+rela[y])%3;

种类并查集的问题我们必须清楚初始状况是什么样的还有状态的转移方程是什么。

代码如下:

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 #define maxn 50005
 5 int n,k;
 6 int fa[maxn],rela[maxn];//分别表示父亲、子节点和父节点的关系 
 7 int ans;
 8 void init()
 9 {
10     for(int i=1;i<=n;i++)
11     {
12         fa[i]=i;      /*relation:0、同类 1、吃2、被吃*/ 
13         rela[i]=0;//初始状态下它和本身在一个集合中,关系为同类 
14     }
15     ans=0;
16 }
17 int find(int x)
18 {
19     if(x==fa[x])return fa[x];
20     else
21     {
22         int tmp=fa[x];
23         fa[x]=find(fa[x]);
24     
25         rela[x]=(rela[tmp]+rela[x])%3;//更新x与x的新的根结点的关系,ppx->x=ppx->px+px->x=rela[ppx]+real[px]; 
26         return fa[x]; 
27     }                                   //向量模式,rela为rootx->x的向量方向;为保证结果在[0,2]区间内,对3取模; 
28 }
29 int main()
30 {
31     cin>>n>>k;
32     init();
33     int f,a,b; 
34     for(int i=1;i<=k;i++)
35     {
36         scanf("%d%d%d",&f,&a,&b);
37         if(a>n||b>n)
38         {
39             ans++;
40             continue;
41         }
42         if(f==2&&a==b)//x吃x ,不合法, 
43         {
44             ans++;
45             continue;
46         }
47         int px=find(a);
48         int py=find(b);
49         if(px!=py)       //如果关系还未建立则建立关系       //合并的操作和查询是否正确的操作在主函数体中一起完成 
50         {
51             fa[py]=px;
52             rela[py]=(3+rela[a]+(f-1)-rela[b])%3;
53         }
54         else
55         {
56             if(f==1&&rela[a]!=rela[b])//说是同类然而 计算得并不是,则说了假话 
57             {
58                 ans++;
59                 continue;
60             }
61             if(f==2&&(3+rela[b]-rela[a])%3!=f-1)//两者之间三种状态题目中只有两种描述方式 
62             {
63                 ans++;
64                 continue;
65             }
66         }
67         
68      } cout<<ans;
69  } 

 下面是分集合操作的代码,集合划分成三个 i是同类集合,n+i是食物集合,2*n+i表示天敌集合,三个集合是绝对不相交的。

代码如下:(因为只有吃,被吃,吃三种状态,所以检测的时候可能错误的状态就是两种)

 1 #include<cstdio>  
 2 #include<iostream>
 3 using namespace std;
 4 int fa[150100];  
 5 int x1,x2,x3,y1,y2,y3;
 6 int n,k,d,x,y,ans=0;
 7 int find(int x)
 8 {
 9     return fa[x]==x?x:fa[x]=find(fa[x]);
10 }
11 int main()
12 {
13     scanf("%d%d",&n,&k);
14     for(int i=1;i<=n*3;i++) fa[i]=i;
15     for(int i=1;i<=k;i++)
16     {
17         scanf("%d%d%d",&d,&x,&y);
18         if(x>n||y>n)
19         {
20             ans++;
21             continue;
22         }
23         x1=find(x);        //x表示自己
24         x2=find(x+n);    //x+n表示x的食物
25         x3=find(x+n*2); //x+2*n表示x的天敌
26         y1=find(y);        //y表示自己
27         y2=find(y+n);    //y+n表示y的食物
28         y3=find(y+n*2);    //y+2*n表Y的是天敌
29         if(d==1)
30         {
31             if(x1==y2||y1==x2) ans++;
32             //给出的话表明x和y是同类,但是检测发现x吃y或者y吃x,产生矛盾。
33             //在这次检测之中和x,y的天敌集合是没有关系的,因为天敌只是对一方的天敌
34             //比如说x的天敌可能是y的同类,也可能是食物,也可能是天敌,所以说,是没有定论的美不需要检测 
35             else
36             {
37                 fa[x1]=y1;//由于x和y是同类,所以他们的同类,天敌,食物都是一样的,所以需要全部合并 
38                 fa[x2]=y2; 
39                 fa[x3]=y3;
40             }
41         }
42         else
43         {
44             if(x1==y1||x1==y2) ans++;//给出的话中表明x吃y,但是检测发现x和y是同类或者y吃x,发生矛盾 
45             else
46             {//集合的合并 
47                 fa[y1]=x2;//y的同类都是x的食物 
48                 fa[y2]=x3;//x吃y,y吃z,z吃x 
49                 fa[y3]=x1;//将x集合归入y的天敌集合 
50             }
51         }
52     }
53     printf("%d",ans);
54     return 0;
55 }

 

原文地址:https://www.cnblogs.com/randy-lo/p/12563371.html