【OI】(带权并查集)[HNOI2005]狡猾的商人

传送门:[HNOI2005]狡猾的商人

其实带权并查集这东东还算比较好理解的啦。只是看了一堆blog我也不太懂他们在讲啥,所以就自己臆想了一下每个数组的意义然后自己推了公式,结果发现自己推出来的和他们代码里写的是等价的所以就愉快地写了代码然后就AC啦。

因此我就讲讲带权并查集的思路吧。

首先你需要会传统的并查集(这个我相信大家都会)

然后与传统的不同,带权并查集有一个新的数组叫做 v[i] ,他表示的意义是:从当前节点到根节点有向距离(或者在这题称为盈亏利润,这里定义A到B的有向距离为dis时,B到A的有向距离为-dis),而对于两个点对 (x,y) 的距离,如果他们不在一个集合里则肯定没有距离(显然),而他们在集合里的有向距离则为 v[y]-v[x] (相当于是y到根节点的距离根节点到x点的距离)。

好了你既然理解了v数组的含义,你就大可随便推推式子就知道加权并查集怎么写了。

举个例子:

蓝边表示当前要加入的边,我们先讨论合并两个不同集合的情况

显然,在合并之前, fa[]=\{1,1,3,3\}

显然, find(2) = 1 , find(4) = 3

不妨使 f[find(2)] = find(4) \Leftrightarrow f[1] = 3

那么在两个集合合并之前,3所在的集合元素的v数组中的值不用变(因为他们的根还是3)。

而对于1所在集合,他们的根变成了3,而因为原集合的所有元素是指向1的,事实上我们只要改变1的v数组值就行(类似于并查集中合并祖先的思想(好吧就是一样的))。

好现在开始手推 v[1] 的值,其实 v[1] = -v[2] - w + v[4] (自己想想为什么),为了更加公式化一点,我们设y到x的一条边距离为w。fx,fy分别为x,y的根节点,令 fa[fx]=fy 。则有: v[fx] = -v[x] - w + v[y]

于是我们能得到下面的代码:

inline void addedge (int x, int y, int w)
{
	int fx, fy;
	fx = find(x);
	fy = find(y);
	if (fx != fy)
	{
		fa[fx] = fy;
		v[fx] = - v[x] + v[y] - w;
	}
	else
		if (v[y] - v[x] != w)
			fl = 0;
}

接下来我们再讨论如何由根节点更新同集合中的点(也就是findfather的写法)其实核心思想是差不多的。

红色是每个点原来的v值,蓝色是修改后的v值

容易想到,根节点肯定是第一个修改v值的,我们记i点修改后的v值为 v'[i] ,记根节点为root,则首先改变的则是 v'[root] ,因为v数组以及v'数组的相对大小是不变的(因为这个集合中的path是不变的。说人话就是 v[i]-v[j] = v'[i] - v'[j] ,且i,j都在这个集合里)因此我们可以从根节点开始,逐层改变节点的v值,因为根节点的v值肯定是0,因此上面的式子可以写成: v'[i]=v[i] + v'[root] 因此我们可以写成这个样子:

inline int find (int x)
{
	if (x == fa[x]) return x;
	int t = find(fa[x]);//找根节点
	v[x] += v[fa[x]];//逐层累加
	return fa[x] = t;
}

对于狡猾的商人这道题,就在上面的基础上加个前缀和思想就行啦。

(具体来说就是从y点到x-1点连一条权值为w的边,然后跑上面的权值并查集就行了)

下附代码(思路和上面的有些不一样,其实是等价的。读者可以看着我的代码再推一遍式子看看对不对,其实主要不一样的原因就在于我代码里合并集合写的是 fa[fy]=fx

代码如下:

#include <cstdio>
#include <algorithm>
#define fo(i, a, b) for(int i = (a); i <= (b); i++)
#define N 105
int t, n, m, x, y, w, fa[N], v[N];
bool fl;
inline int find (int x)
{
    if (x == fa[x]) return x;
    int t = find(fa[x]);
    v[x] += v[fa[x]];
    return fa[x] = t;
}
inline void addedge (int x, int y, int w)
{
    int fx, fy;
    fx = find(x);
    fy = find(y);
    if (fx != fy)
    {
        fa[fy] = fx;
        v[fy] = v[x] - v[y] + w;
    }
    else
        if (v[y] - v[x] != w)
            fl = 0;
}
int main()
{
    scanf("%d", &t);
    while (t--)
    {
        fl = 1;
        scanf("%d %d", &n, &m);
        fo(i, 0, n) {fa[i] = i; v[i] = 0;}
        fo (i, 1, m)
        {
            scanf("%d %d %d", &x, &y, &w);
            addedge(x - 1, y, w);
        }
        if (fl) printf("true\n"); else printf("false\n");
    }
    return 0;
}

本文为蒟蒻所作,如有言语欠严谨之处请在评论区指出,谢谢。

编辑于 2018-03-25