POJ 1182食物链(分集合以及加权两种解法) 种类并查集的经典
题目链接:http://icpc.njust.edu.cn/Problem/Pku/1182/
题意:给出动物之间的关系,有几种询问方式,问是真话还是假话。
定义三种偏移关系:
x->y 偏移量0时 x和y同类
x->y 偏移量1时 x被y吃
x->y 偏移量2时 x吃y
定义 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 }
每一个不曾起舞的日子,都是对生命的辜负。