【[HNOI2005]狡猾的商人】

加权并查集

由于给出信息的是一些一个区间的和为多少,我们显然并不好处理出每一个点应该为多少,这我们根本做不到

但是我们想一下,如果要求一个区间\([l,r]\)的和,那么我们是不是可以利用前缀和\(p[r]-p[l-1]\)得到

所以一组信息\(l,r,k\)其实可以利用前缀和写成\(p[r]-p[l-1]=k\)的形式

于是我们可以考虑将这些这前缀和的信息利用并查集来合并,同时我们还要存储一个\(s[i]\)表示\(p[fa[i]]-p[i]\)为多少,即根节点与每个点的差为多少

如果对于一条信息\(l,r,k\)\(l-1,r\)已经有了关系,即\(l-1\)\(r\)在一个并查集里,我们就看一看\(p[r]-p[l-1]\)\(p[root]-p[l-1]-(p[root]-p[r])=s[x-1]-s[y]\)是否等于\(k\)就好了

如果\(l-1\)\(r\)没有关系,那么就将这两个并查集合并一下,这里也要推一下式子(刚上来这里的式子推错了,真尴尬)

之后就是一个加权并查集的板子了

#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define maxn 101
int fa[maxn],s[maxn];
inline int read()
{
    char c=getchar();
    int x=0,r=1;
    while(c<'0'||c>'9'){if(c=='-') r=-1;c=getchar();}
    while(c>='0'&&c<='9')
      x=(x<<3)+(x<<1)+c-48,c=getchar();
    return x*r;
}
int find(int x)
{
    if(fa[x]==x) return x;
    int an=find(fa[x]);
    s[x]+=s[fa[x]];
    return fa[x]=an;
}
int n,m,T;
int main()
{
    T=read();
    while(T--)
    {
        n=read();
        m=read();
        for(re int i=0;i<=n;i++) fa[i]=i,s[i]=0;
        int opt=1;
        int x,y,z;
        for(re int i=1;i<=m;i++)
        {
            x=read();
            y=read();
            z=read();
            int xx=find(x-1);
            int yy=find(y);
            if(xx==yy) 
            {
                if(s[x-1]-s[y]!=z) opt=0;
            }
            else
            {
                if(!opt) break;
                fa[xx]=yy;
                s[xx]=-s[x-1]+s[y]+z;
            }
            if(!opt) break;
        }
        if(!opt) puts("false");
        else puts("true");
    }
    return 0;
}
posted @ 2019-01-02 12:03  asuldb  阅读(160)  评论(0编辑  收藏  举报