题意:三类动物A、B、C构成食物链循环,告诉两个动物的关系(同类或天敌),判断有多少个关系是和正确的冲突。
哈哈,看到这道题,一下子就注意到三类动物,之前我们研究的是两类,现在只是多了一类而已,真的是而已吗?
显然不是,如果我们依照上面的想法做,每来一对动物a b就将他们归于同一个祖先下,最后,只剩下一个连通分支。如果我们仍然设r[x]表示x与根结点的关系,那么就有r[x]=0表示与根结点同类,r[x]=1表示与根结点异类(???貌似有三个类啊??这样无法区分另外两个类啊)。
既然之前的想法不能解决问题,我们回本溯源,带权并查集的本质不就是多了各结点之间的关系吗?不妨大胆设一些关系。
想着想着,因为每来一句话都要判断其真假,所以必须要让所有结点在一个连通分支内(如果分3个分支,来了新的一对动物让你判断,如果发生前后矛盾,你将不知道是之前是错的还是现在的是错的),所以数组r[]要保留,既然是食物链,不妨设r[x]=0表示x与根结点同类,r[x]=1表示x吃根结点,r[x]=-1表示x被根结点吃。这样如果命令是1 a b时,我们便可以先判断它们是否是同一连通分支的,如果是,就判断它们各自与根结点的关系(即r[a]==r[b]),如果r[a]==r[b](即表示它们是同类),命令正确,反之说明这个命令是假话。当然,如果它们不是同一连通分支的,它们之间的关系就不好判定,命令真假也就无法确定,我们只能将两个点连入同一连通分支,即直接调用unite函数。如果命令是2 a b时(即a吃b),我们仍然先判断它们是否是同一连通分支的,如果是,我们可以穷举r[a]和r[b]的值来判断此话是否正确(若想命令正确只能是:r[a]=1,r[b]=0或r[a]=0,r[b]=-1或r[a]=-1,r[b]=1)。当然,如果不是同一连通分支的,同上,直接调用unite函数。
于是,我们可以得出下面的代码:
cin>>D>>a>>b;
if(D==1){
if(find(a) == find(b)) //a和b属于同一连通分量
if(r[a] != r[b]) //a和祖先的关系 与 b和祖先的关系 不一致
假话数量++; //即a和b的种类不一样
else
unite(a,b);
}
if(D==2){
if(find(a)==find(b))
if(不是正确的对应关系)
假话数量++;
else
unite(a,b);
}
看到这,我们目标就有啦啦啦!!至于关系的得到就锁定在find函数和unite函数内了,详细的推导日后再说。
我怀着自信满满的心去编程,却发现有个地方忽视了。。。当命令D a b过来时,如果a和b属于同一连通分支还好说,如果不是,我们就要unite(a,b),但是unite的具体内容决定r[]的更新,所以与当前传入的D有关。
于是,得到下面的代码:
cin>>D>>a>>b;
int sum = 0; //假话数量
if(a>n || b>n || (D==2&&x==y)) //如果节点编号大于最大编号,或者自己吃自己,说谎
sum++;
else if(find(a) == find(b)){
if(D==1 && r[a]!=r[b]) //如果 x 和 y 不属于同一类
sum++;
if(D==2 && (r[a]+1)%3!=r[b]) //如果a没有吃b(注意要对应unite(x,y)的情况,否则一路WA到死啊!!!)
sum++;
}
else{
unite(a,b,d); //如果开始没有关系,则建立关系
于是相应的代码就有了:
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn = 50000+10;
int pre[maxn]; //存父亲节点
int r[maxn]; //存与根节点的关系
//r[x]==0表示x与根结点同种类 r[x]==1代表x会被根结点吃 r[x]==2代表x会吃根结点
int n,k;
void init()
{
for(int i=1;i<=n;i++){
pre[i] = i;
r[i] = 0; //初始每个动物都与自己同种类
}
}
int find(int x)
{
if(x == pre[x]) return x;
int t = pre[x];
pre[x] = find(pre[x]);
//回溯由子节点与父节点的关系和父节点与根节点的关系找子节点与根节点的关系
r[x] = (r[x]+r[t])%3; //此处模3
return pre[x];
}
void unite(int x,int y,int d)
{
int fx = find(x);
int fy = find(y);
pre[fy] = fx; //合并树 注意:被x吃,所以以 x的根为父
r[fy] = (r[x]-r[y]+3+(d-1))%3; //对应更新与父节点的关系
}
int main()
{
cin>>n>>k;
init();
int sum = 0,D,a,b;
while(k--){
scanf("%d%d%d",&D,&a,&b);
if(a>n || b>n || (a==b&&D==2)){
sum++;
continue;
}
else if(find(a) == find(b)) //如果原来有关系,也就是在同一棵树中,那么直接判断是否说谎
{
if(D == 1 && r[a] != r[b]) sum++; //如果a和b不属于同一类
if(D == 2 && (r[a]+1)%3 != r[b]) sum++; // 如果a没有吃b (注意要对应unite(a,b)的情况,否则一路WA到死啊!!!)
}
else unite(a,b,D); //如果开始没有关系,则建立关系
}
printf("%d\n",sum);
return 0;
}
poj1182
写到这,只缺一些关系的证明了。我也不想证,怎么办??搬点别人的货吧。
——————————————食物链别人讲解————————
思路:把确定了相对关系的节点放在同一棵树中
每个节点对应的 r[]值记录他与根节点的关系:
0:同类,
1:被父亲节点吃,
2: 吃父亲节点