您的位置:首页 > 其它

poj 1182 - 食物链(并查集)

2013-10-17 17:39 309 查看
自己找的并查集专题:http://hi.baidu.com/vfxupdpaipbcpuq/item/13cbd3258c29e20d72863edf 

思路:(from:上面专项题解)

首先,集合里的每个点我们都记录它与它这个集合(或者称为子树)的根结点的相对关系relation。0表示它与根结点为同类,1表示它吃根结点,2表示它被根结点吃。

那么判断两个点a, b的关系,我们令p = Find(a), q = Find(b),即p, q分别为a, b子树的根结点。

       1. 如果p != q,说明a, b暂时没有关系,那么关于他们的判断都是正确的,然后合并这两个子树。这里是关键,如何合并两个子树使得合并后的新树能保证正确呢?这里我们规定只能p合并到q(刚才说过了,启发式合并的优化效果并不那么明显,如果我们用启发式合并,就要推出两个式子,而这个推式子是件比较累的活...所以一般我们都规定一个子树合到另一个子树)。那么合并后,p的relation肯定要改变,那么改成多少呢?这里的方法就是找规律,列出部分可能的情况,就差不多能推出式子了。这里式子为 : tree[p].relation
= (tree[b].relation - tree[a].relation + 2 + d) % 3; 这里的d为判断语句中a, b的关系。还有个问题,我们是否需要遍历整个a子树并更新每个结点的状态呢?答案是不需要的,因为我们可以在Find()函数稍微修改,即结点x继承它的父亲(注意是前父亲,因为路径压缩后父亲就会改变),即它会继承到p结点的改变,所以我们不需要每个都遍历过去更新。

       2. 如果p = q,说明a, b之前已经有关系了。那么我们就判断语句是否是对的,同样找规律推出式子。即if ( (tree[b].relation + d + 2) % 3 != tree[a].relation ), 那么这句话就是错误的。

       3. 再对Find()函数进行些修改,即在路径压缩前纪录前父亲是谁,然后路径压缩后,更新该点的状态(通过继承前父亲的状态,这时候前父亲的状态是已经更新的)。


代码如下:

const int M = 50005;
int p[M], flag[M];
int find(int x)
{
int tmp = p[x];
p[x] = (p[x]==x?x:find(p[x]));
flag[x] = (flag[x]+flag[tmp])%3;
return p[x];
}
int main()
{
int n, k, d, a, b, ans;
scanf("%d%d", &n, &k);
ans = k;
for(int i = 1; i <= n; ++i) p[i] = i;
for(int Kcase = 0; Kcase < k; ++Kcase)
{
scanf("%d%d%d", &d, &a, &b);
if(a>n||b>n||(a==b&&d==2)) continue;
int x = find(a);
int y = find(b);
if(x!=y)
{
p[x] = y;
flag[x] = (flag[b]-flag[a]+2+d)%3;
ans -= 1;
}
else
ans-=((flag[b]-flag[a]+d)%3==1);
}
printf("%d\n", ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: