带权并查集&&并查集

Posted ylrwj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带权并查集&&并查集相关的知识,希望对你有一定的参考价值。

并查集

一般的并查集主要记录节点之间的链接关系,而没有其他的具体的信息,仅仅代表某个节点与其父节点之间存在联系,它多用来判断图的连通性

主要操作有:

初始化
把每个点所在集合初始化为其自身。通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        q[i]=i;
    }
}

 

查找
查找元素所在的集合,即根节点。
int find(int x)
{
    int h=x;
    if(h==q[h])
        return h;
    else
    {
        q[h]=find(q[h]); 
        return q[h];
    }
}

 

合并
将两个元素所在的集合合并为一个集合。通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现
void unite(int x,int y)
{
    k=find(x);
    f=find(y);
    if(k!=f)
    {
        q[k]=f;
    }
}

(来一个模板题)

Today is Ignatius‘ birthday. He invites a lot of friends. Now it‘s dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers.

One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table.

For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least.

InputThe input starts with an integer T(1<=T<=25) which indicate the number of test cases. Then T test cases follow. Each test case starts with two integers N and M(1<=N,M<=1000). N indicates the number of friends, the friends are marked from 1 to N. Then M lines follow. Each line consists of two integers A and B(A!=B), that means friend A and friend B know each other. There will be a blank line between two cases.
OutputFor each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks.
Sample Input

2
5 3
1 2
2 3
4 5

5 1
2 5

Sample Output

2
4
题解:
该题就是说xx的朋友中,认识的可以做一桌,如a认识b,b认识c,则他们可以一桌,现在给了互相之间的关系,求至少需要几桌,此处用并查集就是把认识的归一类,代码如下
#include<cstdio>
#include<iostream>
using namespace std;
int T;
int n,m,a,b;
int k,f;
int q[1010];
int find(int x)
{
    int h=x;
    if(h==q[h])
        return h;
    else
    {
        q[h]=find(q[h]); 
        return q[h];
    }
}
void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        q[i]=i;
    }
}
void unite(int x,int y)
{
    k=find(x);
    f=find(y);
    if(k!=f)
    {
        q[k]=f;
    }
}
int main()
{
    cin>>T;
    while(T--)
    {
        int ans=0;
        cin>>n>>m;
        init(n);
        for(int i=0;i<m;i++)
        {
            cin>>a>>b;
            unite(a,b);
        }
        for(int i=1;i<=n;i++)
        {
            if(q[i]==i)
              ans++;
        }
        cout<<ans<<endl;
    }
    return 0;
 } 

带权并查集

带权并查集就是附带其他信息的并查集,带权并查集需要用到一个路径压缩的知识点,将每个节点直接与其Find()操作最终得到的节点链接,就是所谓的路径压缩

用find就可以操作,与一般的并查集相比,它只是在find(parent[x])前边加了一步赋值操作,将在查找过程中遇到的所有的节点的父节点都设为最终得到的那个节点。

int Find(int x)
{
    if (x != parent[x])
    {
        int i = parent[x];
        parent[x] = Find(parent[x]);
        sum[x] += sum[i];
    }
    return parent[x];
}

权值则一般都是两个节点之间的某一种相对的关系,但是考虑到权值就会有两个问题:

1.每个节点都记录的是与根节点之间的权值,那么在Find的路径压缩过程中,权值也应该做相应的更新,因为在路径压缩之前,每个节点都是与其父节点链接着,那个Value自然也是与其父节点之间的权值
2.在两个并查集做合并的时候,权值也要做相应的更新,因为两个并查集的根节点不同。
                       int fl = Find(l);
            int fr = Find(r);
            if (fl == fr)
            {
                if ((sum[l] - sum[r]) != value)
                {
                    ans++;
                }
            }
            else {
                parent[fl] = fr;
                sum[fl] = -sum[l] + sum[r] + value;
            }

具体问题具体分析。

例题:

刁姹接到一个任务,为税务部门调查一位商人的账本,看看账本是不是伪造的。账本上记录了n个月以来的收入情况,其中第i 个月的收入额为Ai(i=1,2,3...n-1,n), 。当 Ai大于0时表示这个月盈利Ai 元,当 Ai小于0时表示这个月亏损Ai 元。所谓一段时间内的总收入,就是这段时间内每个月的收入额的总和。 刁姹的任务是秘密进行的,为了调查商人的账本,她只好跑到商人那里打工。她趁商人不在时去偷看账本,可是她无法将账本偷出来,每次偷看账本时她都只能看某段时间内账本上记录的收入情况,并且她只能记住这段时间内的总收入。 现在,刁姹总共偷看了m次账本,当然也就记住了m段时间内的总收入,你的任务是根据记住的这些信息来判断账本是不是假的。

Input

第一行为一个正整数w,其中w < 100,表示有w组数据,即w个账本,需要你判断。每组数据的第一行为两个正整数n和m,其中n < 100,m < 1000,分别表示对应的账本记录了多少个月的收入情况以及偷看了多少次账本。接下来的m行表示刁姹偷看m次账本后记住的m条信息,每条信息占一行,有三个整数s,t和v,表示从第s个月到第t个月(包含第t个月)的总收入为v,这里假设s总是小于等于t。

Output

包含w行,每行是true或false,其中第i行为true当且仅当第i组数据,即第i个账本不是假的;第i行为false当且仅当第i组数据,即第i个账本是假的。

Sample Input2                                       
3 3                                     
1 2 10
1 3 -5
3 3 -15
5 3
1 5 100
3 5 50
1 2 51

Sample Outputtrue
false

代码如下:

/*#include<cstdio>
#include<iostream>
using namespace std;
bool flag;
const int maxn=1010;
int parent[maxn],sum[maxn];
int find(int x)
{
    if(x!=parent[x])
    {
        int i=parent[x];
        parent[x]=find(parent[x]);
        sum[x]+=sum[i];
    }
    return parent[x];
}
void Union(int x,int y,int z)
{
   int kx,ky;
   kx=find(x);
   ky=find(y);
   if(kx!=ky)
   {
       parent[ky]=kx;
       sum[ky]=sum[x]+z-sum[y];
    }
    else
    {
        if(sum[y]-sum[x]!=z)
          flag=false;
    }    
}
int main()
{
    int w,n,m,s,t,v;
    cin>>w;
    while(w--)
    {
        cin>>n>>m;
        flag=true;
        for(int i=0;i<n;i++)
           parent[i]=i;
        for(int i=0;i<m;i++)
        {
            cin>>s>>t>>v;
            Union(s,t,v);
        }
        if(!flag)
           cout<<"false"<<endl;
        else
           cout<<"true"<<endl;
    }
    return 0;
}*/


#include <iostream>
#include <cstdio> 
using namespace std;

const int maxn= 200005;
int parent[maxn];
int sum[maxn];
int Find(int x)
{
    if (x != parent[x])
    {
        int i = parent[x];
        parent[x] = Find(parent[x]);
        sum[x] += sum[i];
    }
    return parent[x];
}
int main()
{
    int t;
    cin>>t;
    int m, n;
    int ans = 0;
    while (t--)
    {
        cin>>m>>n;
        for (int i = 0; i <= m; i++)
        {

            parent[i] = i;

            sum[i] = 0;
        }
        ans = 0;
        while (n--)
        {
            int l, r, value;
            cin >> l >> r >> value;
            l--;
            int fl = Find(l);
            int fr = Find(r);
            if (fl == fr)
            {
                if ((sum[l] - sum[r]) != value)
                {
                    ans++;
                }
            }
            else {
                parent[fl] = fr;
                sum[fl] = -sum[l] + sum[r] + value;
            }
        }
       if(ans)
         cout << "false" << endl;
       else
         cout<<"true"<<endl;

    }

    return 0;

}
 

这算是带权并查集的模板题,菜鸟我就是直接套用模板了。

 

以上是关于带权并查集&&并查集的主要内容,如果未能解决你的问题,请参考以下文章

并查集&&带权并查集BZOJ3296&&POJ1182

POJ 1182 食物链 (带权并查集 && 向量偏移)

CodeForces - 687D: Dividing Kingdom II (二分图&带权并查集)

简洁而优美的结构 - 并查集 | 一文吃透 “带权并查集” 不同应用场景 | “手撕” 蓝桥杯A组J题 - 推导部分和

POJ 2492 A Bug's Life (带权并查集 && 向量偏移)

「带权并查集」奇偶游戏