带权并查集&&并查集

并查集

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

主要操作有:

初始化

把每个点所在集合初始化为其自身。通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为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;

}
 

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

原文地址:https://www.cnblogs.com/ylrwj/p/10632976.html

时间: 2024-10-28 12:35:23

带权并查集&&并查集的相关文章

hdu3038(带权并查集)

题目链接: http://acm.split.hdu.edu.cn/showproblem.php?pid=3038 题意: n表示有一个长度为n的数组, 接下来有m行形如x, y, d的输入, 表示从第x,个元素到第y个元素的和为d(包括x, 和y), 问m行输入里面有几个是错误的(第一个输入是正确的); 思路: 很显然带权并查集咯,我们可以用距离的概念代替和的概念比较好理解一点,d表示x到y的和即x到y的距离; 可以用rank[x]表示x到其父亲节点的距离,  将正确的距离关系合并到并查集中

【POJ1182】 食物链 (带权并查集)

Description 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种. 有人用两种说法对这N个动物所构成的食物链关系进行描述: 第一种说法是"1 X Y",表示X和Y是同类. 第二种说法是"2 X Y",表示X吃Y. 此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的.当一句话满足下列三条之

【poj 1988】Cube Stacking(图论--带权并查集 模版题)

题意:有N个方块,M个操作{“C x”:查询方块x上的方块数:“M x y”:移动方块x所在的整个方块堆到方块y所在的整个方块堆之上}.输出相应的答案. 解法:带权并查集.每堆方块作为一个集合,维护3个数组:fa[x]表示x方块所在堆的最顶部的方块:d[x]表示x方块所在堆的最底部的方块:f[x]表示x方块方块x上的方块数. 1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<

并查集练习2(带权并查集)

明天旅游去爬山逛庙玩,今天练一天然后早早睡觉啦~ poj1703 Find them, Catch them (带权并查集) 1 #include<cstdio> 2 const int N=1e5+1; 3 int f[N]; 4 int r[N];//表示与父节点的关系,0同类,1不同类 5 int n; 6 void init(){ 7 for(int i=1;i<=n;++i){ 8 f[i]=i; r[i]=0; 9 } 10 } 11 int fin(int x){ 12 i

Travel(HDU 5441 2015长春区域赛 带权并查集)

Travel Time Limit: 1500/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)Total Submission(s): 2404    Accepted Submission(s): 842 Problem Description Jack likes to travel around the world, but he doesn’t like to wait. Now, he is tr

poj1417 带权并查集+0/1背包

题意:有一个岛上住着一些神和魔,并且已知神和魔的数量,现在已知神总是说真话,魔总是说假话,有 n 个询问,问某个神或魔(身份未知),问题是问某个是神还是魔,根据他们的回答,问是否能够确定哪些是神哪些是魔. 对于这些问题,我们只需要发现,如果回答对方是魔,那么即可以判断出这两个不是同一种族,而如果回答对方是神,那么说明这两个是同一种族,那么就可以用带权并查集合并这些神和魔,然后记录两种分别多少个,这样当所有询问都处理完时我们就可以得到一系列的集合,每个集合分别有它的两个种族的人数,但是此时对于每个

Lightoj1009 Back to Underworld(带权并查集)

转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Back to Underworld Time Limit:4000MS     Memory Limit:32768KB     64bit IO Format:%lld & %llu Description The Vampires and Lykans are fighting each other to death. The war has become so fierc

[NOIP摸你赛]Hzwer的陨石(带权并查集)

题目描述: 经过不懈的努力,Hzwer召唤了很多陨石.已知Hzwer的地图上共有n个区域,且一开始的时候第i个陨石掉在了第i个区域.有电力喷射背包的ndsf很自豪,他认为搬陨石很容易,所以他将一些区域的陨石全搬到了另外一些区域. 在ndsf愉快的搬运过程中,Hzwer想知道一些陨石的信息.对于Hzwer询问的每个陨石i,你必须告诉他,在当前这个时候,i号陨石在所在区域x.x区域共有的陨石数y.以及i号陨石被搬运的次数z. 输入描述: 输入的第一行是一个正整数T.表示有多少组输入数据. 接下来共有

hdu 1558 Segment set【基础带权并查集+计算几何】

Segment set Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 3599    Accepted Submission(s): 1346 Problem Description A segment and all segments which are connected with it compose a segment set

Corporative Network(带权并查集)

这个题的题意是  当输入'E'是查找操作,查找从后面这个数到他的父亲这边的值,'I'代表把后面的数作为前面数的父亲 然后他们两个的差值代表这两个边的权值 水水的题 #include <stdio.h> #include <string.h> int par[20005]; int rank1[20005]; int abs(int hh) { return (hh>0)?hh:-hh; } void init() { for(int i=0;i<20005;i++) {