1. 程式人生 > >HDU3038 How Many Answers Are Wrong【巧妙並查集】

HDU3038 How Many Answers Are Wrong【巧妙並查集】

題目連結

思路

題意就是說,不停的給你區間和,問你和前面已給出的矛盾的有幾個。

首先,對於給定的一系列區間[a, b],只有有某個點相鄰的區間,我們才能把它結合,得出新的資訊。

像[1, 8], [6, 10]這樣的不相鄰的區間,我們得不出除了他們倆本身以外的任何資訊。

但例如[1, 10], [1,3], [6,10],我們就能把他們結合,得出幾個新區間資訊:[4, 5], [1, 5], [4, 10]。

然後,這時候如果我們把左端點減個1,化為左開右閉的區間,就相當於,我們通過(0, 10], (0, 3], (5, 10]推出了(3, 5], (0, 5], (3, 10],也就是說,我們推出了{0, 3, 5, 10}裡任取兩個當端點的所有區間的資訊。

怎麼樣,是不是有點並查集的味道了?也就是說,只要有端點相同,我們可以把這些點歸到一個集合裡,然後這個集合裡所有的端點,任取兩個,我們都能得出他的資訊。

所以現在我們能做到,對於給定的一個區間(a-1, b],我們可以判斷出,這個區間的資訊能不能通過以前的區間得到,如果不能,把兩個端點所屬的集合合併。如果能,判斷當前給的區間和,是不是和推出來的區間和相等,如果相等沒問題,否則答案++。

那接下來的難點就是,怎麼得到這個推出來的區間和?我們現在只能判斷能不能推出來,但不知道推出來是多少。

這就是這題最巧妙的地方了,我們記一個值w[i],表示並查集裡i這個節點到他父節點的距離,也就是說設sum[i]為(0, i]的區間和,w[i] = sum[i] - sum[fa[i]]。

然後,因為並查集路徑壓縮的特點,每次find之後,每個集合都是成三角形的形狀,所有子節點都直接指向唯一一個父節點。

所以,如果我們能把w[i]維護好,區間(a, b]的和可以很輕鬆的算出來:

sum[b] - sum[a]

= sum[b] - sum[fa[b]] - (sum[a] - sum[fa[a]])

因為fa[b] = fa[a](路徑壓縮的特點),所以

= w[b] - w[a]

那麼接下來就是怎麼維護好這個w了。

首先w發生變動的地方有兩個,一個是find過程中的路徑壓縮,這時候w只要加上他父節點的w值即可。

還有一處就是合併集合的時候,設輸入的集合為(a, b],和為c,a到他集合的父節點的距離為x,b到父節點的距離為y,合併a,b集合時,a所處集合的父節點到b所處的集合的父節點距離為z。

則有等式,x + z - y = c,得z = y - x + c,即z = w[b] - w[a] +c。

至此,本題就完了。

這題並查集真的用的十分巧妙,需要好好理解。

AC程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
#define CLR(x,y) memset((x),(y),sizeof(x))

const int N = 200000 + 100;
int pre[N], w[N];
int find(int x)
{
    if (x == pre[x])return x;
    else
    {
        int fa = find(pre[x]);
        w[x] += w[pre[x]];
        pre[x] = fa;
        return fa;
    }
}
int main()
{
    int n, m;
    while (scanf("%d%d", &n, &m) != EOF)
    {
        for (int i = 0; i <= n; ++i)
        {
            pre[i] = i;
            w[i] = 0;
        }
        int ans = 0;
        while (m--)
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            a--;
            int x = find(a), y = find(b);
            if (x == y)
            {
                if (w[b] - w[a] != c)ans++;
            }
            else
            {
                pre[y] = x;
                w[y] = w[a] - w[b] + c;
            }
        }
        printf("%d\n", ans);
    }

    return 0;
}