带权值的并查集整理与练习题

博文:https://blog.csdn.net/yjr3426619/article/details/82315133

带全并查集

路径压缩,表达每个当前node与 当前node所在的并查集的root 之间的关系

并查集一定是单向连通的,所以一些node处理时 【node1,node2】 weight 可能需要把 一端的闭区间变为开区间

俩个相对信息求出绝对信息   --水题

例题 : HDU 3038 

//带权值并查集
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 2e5 + 25;
int dis[maxn];
int p[maxn];
int find(int u)//寻根
{
    if(u!=p[u])
    {
        int tmp = p[u];//保存原来的根节点
        p[u] = find(p[u]);//路径压缩
        dis[u] += dis[tmp];//dis[tmp] 已经为tmp(即原来父节点)到根节点的距离所以加上本身自身到tmp的dis则为u到root的dis  
    }
    return p[u];
}
void merge(int u,int v,int value)
{
    int f1 = find(u);
    int f2 = find(v);
    p[f1] = f2;
    dis[f1] = value + dis[v] - dis[u];
}//带权并查集合并
int main()
{
    int n,m;//n节点,m条边
    while(cin>>n>>m)
    {
        int ans = 0,u,v,value;
        for(int i=0;i<=n;++i)
        {
            dis[i] = 0;
            p[i] = i;
        }
        while(m--)
        {//图改成了从0开始(important)
            cin>>u>>v>>value;
            --u;
            if(find(u)==find(v))
            {
                if((dis[u]-dis[v])!=value)
                    ++ans;
            }else{
                merge(u,v,value);//否则合并
            }
        }
        cout<<ans<<endl;
    }
}

 hihoCoder 1515

//带权并查集
#include<iostream>
#include<cstdio>//不是同一集合,合并,否则不做操作
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1e5 + 15;
int dis[maxn],p[maxn];//当前node到root的距离
int find(int u)
{
    if(u!=p[u])
    {
        int tmp = p[u];
        p[u] = find(p[u]);
        dis[u] += dis[tmp];
    }
    return p[u];
}
int main()
{
    int n,m,q;//n个节点,m条边,q个查询
    while(cin>>n>>m>>q)
    {
        for(int v=1;v<=n;++v)
        {
            dis[v] = 0;
            p[v] = v;
        }
        int u,v,value;
        while(m--)
        {
            cin>>u>>v>>value;
            int f1 = find(u);
            int f2 = find(v);
            if(f1!=f2)
            {
                p[f1] = f2;
                dis[f1] = value + dis[v] - dis[u];
            }
        }
        while(q--)
        {
            cin>>u>>v;
            if(find(u)!=find(v))
                cout<<-1<<endl;
            else
                cout<<dis[u] - dis[v]<<endl;
        }
    }
}  

POJ 2492

//带权并查集(主要用于逻辑判断)      //题量还是少了,思维不够...(真菜)
#include<iostream>//边的权值代表
#include<cstdio>
#include<cstring>//其实就是构建出了一符图,每一个集合内的node都与这个图的root有一个关系,
//通过中间关系root判断条件是否矛盾
#include<algorithm>//POJ 2492 
using namespace std;
const int maxn = 2e3 + 20;
int p[maxn],dis[maxn];
int find(int u)
{
    if(p[u]!=u)
    {
        int tmp = p[u];//保存原来父节点
        p[u] = find(p[u]);
        dis[u] = (dis[u] + dis[tmp]) % 2;
    }
    return p[u];
}
int main()
{
    int kase = 0,T,n,m,u,v;
    scanf("%d",&T);
    while(T--)
    {
        if(kase)
            printf("
");
        printf("Scenario #%d:
",++kase);
        scanf("%d%d",&n,&m);//n个节点,m个异性
        for(int v=1;v<=n;++v)
        {
            p[v] = v;
            dis[v] = 0;
        }
        bool flag = false;
        while(m--)
        {
            scanf("%d%d",&u,&v);
            if(flag)
                continue;
            int f1 = find(u);
            int f2 = find(v);
            if(f1==f2)
            {
                if(dis[u]==dis[v])
                    flag = true;//u ~ v 节点为相同性别
            }else{  
                p[f1] = f2;
                dis[f1] = (1 + dis[v] - dis[u]) % 2;
            }
        }
        if(flag)
            printf("Suspicious bugs found!
");
        else
            printf("No suspicious bugs found!
");
    }
}

 POJ 1128

思路还是很简单的,权值为0为同类,1 为 A 吃 B,2 为 A B 被 B 吃

简单推下关系就好了

//#include<bits/stdc++.h>//带权并查集(处理相对问题)
//#include<array>
#include<iostream>//加了个关闭输入流,一直WA,感受到了测评姬深深恶意
#include<cstdio>
#define inf (0x3f3f3f3f)//状态的选择一定是在merage时,so 不存在一条边既表示吃又表示被吃的关系,题量少了,入了坑...
using namespace std;//POJ1128 食物链
const int maxn = 5e4 + 15;
//array<int,maxn> p;
//array<int,maxn> dis;
int p[maxn],dis[maxn];
int find(int node)
{
    if(node!=p[node])
    {
        int tmp = p[node];
        p[node] = find(p[node]);
        dis[node] = (dis[tmp] + dis[node]) % 3;
    }
    return p[node];
}
int main()
{
    int n,k;
    int cmd,u,v;
    cin>>n>>k;
    int ans = 0;
    for(int i=1;i<=n;++i)
    {
        p[i] = i;
        dis[i] = 0;
    }
    while(k--)
    {
        scanf("%d%d%d",&cmd,&u,&v);
        if(u>n||v>n||(cmd==2&&u==v))
        {
            ++ans;
            continue;
        }
        int f1 = find(u);
        int f2 = find(v);
        if(f1!=f2)
        {
            p[f1] = f2;
            dis[f1] = (cmd - 1 + dis[v] - dis[u]) % 3; 
        }else{
            if(cmd-1!=(dis[u]-dis[v]+3)%3)
                ++ans;
        }   
    }
    cout<<ans<<endl;
    return 0;
}

 POJ 2912 (逆向思维真重要)

#include<iostream>//带权并查集
#include<cstdio>
#include<algorithm>
using namespace std;//(important) 如何找到最先能确定裁判的位置(想了很久,真菜)
// 因为是每次枚举去尝试n个人,哪个人为裁判,如果选中了一个裁判编号,则证明其他人都不是裁判,所以其余n-1个人的枚举出现矛盾
// so 剩余的n - 1个出现的矛盾行数最晚的则为最早能确定选定的是裁判的行数
int n,m;
const int maxn = 512;
const int maxx = 2e3 + 48;
int p[maxn],dis[maxn],node1[maxx],node2[maxx];
char Cmp[maxx];
int find(int u)
{
    if(p[u]!=u)
    {
        int parent = p[u];
        p[u] = find(p[u]);
        dis[u] = (dis[u] + dis[parent]) % 3;
    }
    return p[u];
}
int main()
{
    int u,v;
    char cmp;
    while(scanf("%d%d",&n,&m)==2)
    {// 0 表示等于 1 表示大于 2 表示小于
        for(int i=0;i!=m;++i)
            scanf("%d%c%d",&node1[i],&Cmp[i],&node2[i]);
        int cnt = 0,pos = 0,person = -1;//可能为裁判的人 最近能判断
        int i,j;
        for(i = 0;i!=n;++i)//枚举每一个人
        {
            for(j=0;j!=n;++j)
            {
                p[j] = j;//森林
                dis[j] = 0;
            }//重置
            for(j = 0;j!=m;++j)
            {
                if(node1[j]==i||node2[j]==i)
                    continue;
                int value;
                if(Cmp[j]=='=')
                    value = 0;
                else if(Cmp[j]=='>')
                    value = 1;
                else
                    value = 2;
                //cout<<node1[j]<<" "<<node2[j]<<endl;
                int f1 = find(node1[j]);
                int f2 = find(node2[j]);
                //cout<<f1<<" "<<f2<<endl;
                if(f1 != f2)//如果不存在关系
                {
                    p[f1] = f2;
                    dis[f1] = ( dis[node2[j]] + value - dis[node1[j]] + 3) % 3; 
                }else{
                    if(value != ( dis[node1[j]] - dis[node2[j]] + 3) % 3)
                    {//起冲突
                        pos  = max(pos,j+1);
                        break;
                    }
                }
            }
            if(j==m)
            {
                ++cnt;
                person = i;
            }
        }
        if(!cnt)
            printf("Impossible
");
        else if(cnt>1)
            printf("Can not determine
");
        else{
            printf("Player %d can be determined to be the judge after %d lines
",person,pos);
        }
    }
}

  POJ1456 水题,不知道为什么归为并查集....

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn  = 1e4 + 32;
typedef long long i64;
int n;
typedef struct{
    int value,weight;
}node;
bool cmp(const node& n1,const node& n2)
{
    if(n1.value!=n2.value)
        return n1.value > n2.value;
    return n1.weight > n2.weight;
}
bool vis[maxn];
vector<node> v;
int main()
{
    ios::sync_with_stdio(false);    cin.tie(0),cout.tie(0);
    while(cin>>n)
    {
        v.clear();
        memset(vis,false,sizeof(vis));
        node tmp;
        for(int i=0;i!=n;++i)
        {
            cin>>tmp.value>>tmp.weight;
            v.push_back(tmp);
        }
        sort(v.begin(),v.end(),cmp);
        i64 sum = 0;
        for(int i=0;i!=n;++i)
        {
            int pos = 0;
            for(int j=v[i].weight;j>=1;--j)
            {
                if(!vis[j])
                {
                    vis[j] = true;
                    pos = j;
                    break;
                }
            }
            if(pos!=0)
                sum += v[i].value;
        }
        cout<<sum<<'
';
    }
}

  

 POJ 1984 https://vjudge.net/problem/POJ-1984

//一道比较有意思的题目,刚开始没管方向,带权并查集胡乱一搞

思路:因为求的是 节点 x 和 y 的 曼哈顿距离,所以单纯只计算节点之间距离会有问题,会出现样例中的情况,即可能存在更小的距离

所以要分为 disx 和 disy 俩个方向,它们表示的是与并查集中的根的相对关系(即可能为正也可能为负,正负表示方向,分别带权并查集计算与跟节点的距离就好了)

//#include<bits/stdc++.h>//带权并查集
#include<vector>
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 4e4 + 32;
int p[maxn],disx[maxn],disy[maxn];//分别计算根节点
typedef long long i64;
int n,m,q;
typedef struct{
    int u,v,value;
    char pos;
}node;
typedef struct{
    int u,v,time,index;//index 查询的位值
}answer;
typedef struct{
    int len,index;
}nodeEnd;
bool cmp(const answer& a1,const answer& a2)
{
    return a1.time < a2.time;
}
bool cmp2(const nodeEnd& e1,const nodeEnd& e2)
{
    return e1.index < e2.index;
}
vector<node> v;
vector<answer> an;
vector<nodeEnd> ne;
int find(int u)
{
    if(p[u]!=u)
    {
        int parent = p[u];
        p[u] = find(p[u]);
        disx[u] += disx[parent];
        disy[u] += disy[parent];
    }
    return p[u];
}
int main()
{
    ios::sync_with_stdio(false); cin.tie(0),cout.tie(0);
    while(cin>>n>>m)
    {
        v.clear();
        an.clear();
        ne.clear();
        for(int i=1;i<=n;++i)
        {
            p[i] = i;
            disx[i] = disy[i] = 0;
        }//init()
        node tmp;
        for(int i=0;i!=m;++i)
        {
            cin>>tmp.u>>tmp.v>>tmp.value>>tmp.pos;
            v.push_back(tmp);
        }
        answer tmpa;
        cin>>q;
        for(int i=0;i!=q;++i)
        {
            cin>>tmpa.u>>tmpa.v>>tmpa.time;
            tmpa.index = i;
            an.push_back(tmpa);
        }        
        sort(an.begin(),an.end(),cmp);
        int cnt = 0;//标签
        for(int i=0;i!=m;++i)
        {
            int f1 = find(v[i].u);
            int f2 = find(v[i].v);
            if(f1 != f2)
            {
                p[f1] = f2;
                if(v[i].pos=='N')
                {
                    disy[f1] = v[i].value + disy[v[i].v] - disy[v[i].u];
                    disx[f1] = disx[v[i].v] - disx[v[i].u];//表达的是相对位置
                }else
                if(v[i].pos=='S')
                {
                    disy[f1] = -v[i].value + disy[v[i].v] - disy[v[i].u];
                    disx[f1] = disx[v[i].v] - disx[v[i].u];
                }
                if(v[i].pos=='E')
                {
                    disx[f1] = v[i].value + disx[v[i].v] - disx[v[i].u];
                    disy[f1] = disy[v[i].v] - disy[v[i].u];
                }else
                if(v[i].pos=='W')
                {
                    disx[f1] = -v[i].value + disx[v[i].v] - disx[v[i].u];
                    disy[f1] = disy[v[i].v] - disy[v[i].u];
                }
            }//匹配
            while(i+1==an[cnt].time)//当时间正好匹配
            {
                nodeEnd tmp;
                tmp.index = an[cnt].index;//下标
                f1 = find(an[cnt].u);
                f2 = find(an[cnt].v);
                if(f1 != f2)
                {
                    tmp.len = -1;
                }else{
                    tmp.len = abs(disx[an[cnt].u] - disx[an[cnt].v]) + 
                    abs(disy[an[cnt].u] - disy[an[cnt].v]);
                }
                ne.push_back(tmp);
                ++cnt; 
            }
        }
        sort(ne.begin(),ne.end(),cmp2);
        for(int i=0;i!=ne.size();++i)
            cout<<ne[i].len<<'
';
    }
}
不怕万人阻挡,只怕自己投降。
原文地址:https://www.cnblogs.com/newstartCY/p/11601164.html