题目链接:
http://poj.org/problem?id=1182
题意:
最多n个动物,每个都有可能是A,B,C三种中的一种,k句话,两种类型:
1.1 x y 代表x与y是用一种
2.2 x y 代表x吃y
输出谎话的数目。
两种做法都是并查集:
第一种:因为每个动物都是这三种的一种,所以为每个动物初始化三种可能x-A x-B x-C分别代表x这个动物属于A,B,C种。用并查集来维护这些关系,在同一个集合中的元素具有相同的真假性质
对于1 x y:检查x与y+n是否在同一个集合和x与y+2n是否在同一个集合 如果有一个为真,表明x与y不是同类,更新结果,否则分别合并(x,y)(x+n,y+n)(x+2n,y+2n),表明x与y要么都属于A要么都属于B要么都属于C。
对于2 x y:检查x与y是否在同一个集合和x与y+2n是否在同一个集合 如果一个为真,表明x与y不是x吃y的关系,更新结果,否则分别合并(x,y+n)(x+n,y+2n)(x+2n,y),表明x吃y,要么为AB要么为BC要么为CA。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 50000*3+5;
int fa[maxn],height[maxn];//fa[i]=i 代表i属于A种类 fa[i+n]代表i属于B种类 fa[i+2n]代表i属于C种类
void ini(int n)
{
for(int i=1;i<=3*n;i++)
{
fa[i]=i;
height[i]=0;
}
}
int findfa(int x)
{
if(fa[x]==x) return x;
return fa[x]=findfa(fa[x]);
}
void unite(int x,int y)
{
x=findfa(x);
y=findfa(y);
if(x==y) return ;
if(height[x]<height[y])
fa[x]=y;
else
{
fa[y]=x;
if(height[x]==height[y]) height[x]++;
}
}
bool same(int x,int y)
{
if(findfa(x)==findfa(y)) return true;
return false;
}
int main()
{
//freopen("in.txt","r",stdin);
int n,k,res;
scanf("%d%d",&n,&k);
{
res=0;
ini(n);
for(int i=1;i<=k;i++)
{
int d,x,y;
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n||x<=0||y<=0) {res++;continue;}
if(d==1)
{//x与y同种
if(same(x,y+n)||same(x,y+2*n)) //矛盾
res++;
else
{
unite(x,y);
unite(x+n,y+n);
unite(x+2*n,y+2*n);
}
}
else
{//x吃y
if(same(x,y)||same(x,y+2*n)) //如果x在A组且y在A组 或者 x在A组y在C组 则矛盾
res++;
else
{
unite(x,y+n);
unite(x+n,y+2*n);
unite(x+2*n,y);
}
}
}
cout<<res<<endl;
}
return 0;
}
第二种:带权并查集。增添一个数组r[i]代表i与其父节点之间的关系,定义0为同类,1为i被fa[i]吃,2为i吃fa[i],关键就是如何维护r[i]数组了。如果两个动物不在一个集合中,表示他们之间的关系还没有定义,直接进行合并操作。如果我们知道1->2和2->3的关系,如何推出1->3的关系,通过列表归纳(看别人题解- -)可以得到如果一个点为a,父节点为fa,爷爷节点为ffa,那么a->ffa=(a->fa+fa->ffa)%3即 r[a]=(r[a]+r[fa])%3,有了这个关系,合并集合的时候就可以推导出那个关系了。具体看代码。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 50005;
int fa[maxn],r[maxn];//fa[i]代表i的父节点 r[i]代表i与父节点的关系 0同种 1被父节点吃 2吃父节点
void ini(int n)
{
for(int i=1;i<=n;i++)
{
fa[i]=i;
r[i]=0;//自己与自己是同种
}
}
int findfa(int a) //返回值为a的父节点
{
if(fa[a]==a) return a;
int faa=fa[a]; //每次对fa[a]递归前先把它记录下来 因为回溯过程中fa[a]已经路径压缩 而递推关系的时候又需要fa[a]
fa[a]=findfa(fa[a]);
r[a]=(r[faa]+r[a])%3; //递推关系
return fa[a];
}
void unite(int a,int b,int d) //合并a与b的关系
{
int faa=findfa(a),fab=findfa(b);//得到的过程中已经进行了路径压缩 即r[a]就是a与它的树根的关系 r[b]就是b与它的树根的关系
if(faa==fab) return ;
fa[fab]=faa;
r[fab]=(r[a]+3-r[b]+d-1)%3; //递推出新的关系
}
int main()
{
//freopen("in.txt","r",stdin);
int n,k,res;
scanf("%d%d",&n,&k);
ini(n);
res=0;
for(int i=1;i<=k;i++)
{
int d,x,y;
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n||x<=0||y<=0) {res++;continue;}
if(d==2&&x==y) {res++;continue;}
int faa=findfa(x),fab=findfa(y);
if(d==1&&faa==fab&&r[x]==r[y]) continue; //在一个关系树中且是同一类
if(d==2&&faa==fab&&(1+r[x])%3==r[y]) continue; //在一个关系树中且是x吃y的关系
if(faa!=fab) //若不在一个关系树中 则不可能出现矛盾 合并关系树 推导出正确的关系
unite(x,y,d);
else
res++; //不符合上面的所有情况则发生了矛盾
}
cout<<res<<endl;
return 0;
}
并查集还是很神奇滴~