蒟蒻林荫小复习——带权并查集

实际上很早之前林荫是有这个技能的。(废话!要不直接叫小学习好了)

众所周知,并查集可以用来维护一些元素之间相连的关系(不知道的出门右转幼儿园)

而状态压缩可以使得并查集查询一对元素的关系的速度变快(O1)

状态压缩之后的并查集实际上是一个由fa数组(相当于单向链表)构成的菊花图

那么,如何用并查集来维护元素之间实际的数量关系呢?

先看一个例子:

假定现在有3个小朋友ABC,B比A大3岁,C比B大2岁。对于这个问题,如果用不带状压的并查集进行表示,那么就可以得到一条链。B到A的边权3表示

B比A大3岁,C到B的边权2表示C比B大2岁。好的我们现在询问C和A的年龄关系。如果一个一个查询这条链上的点那么一定很慢。

是否可以考虑一下,让ABC三者的年龄都与某个人的年龄相关呢(实际上这时就是尝试转化为菊花图)

我们可以考虑让C直接与A相连,但是边权怎么办?

实际上C和A的年龄差就是C和B的差加上B和A的差。那么我们就让菊花图中A——C的边权为dis[B][A]+dis[C][B]

这样的话,我们就可以做到O1查询两个点之间的数量关系了(就比如询问CB年龄差实际上就是CA减去BA)

那么实际上代码也不难实现:

int Find(int x)
{
    if(x==fa[x])
        return x;
    int t=Find(fa[x]);
    dis[x]+=dis[fa[x]];
    fa[x]=t;
    return t;
} 

如果找到最终父亲(就代表这个点是和菊花图的花心相连不用改)直接返回

否则和自己的权就和自己的父亲相加(因为这个时候自己的父亲已经链接到花心上了,权也是父亲和花心的关系)求出自己和花心的关系

至于t为啥一直都是菊花图花心(最终父亲),因为t一直都是Find(fa[x])的结果,fa[x]在对x进行修改时,要么本身就是花心,要么已经和花心直接相连。

下面来两道练手水题:

T1:洛谷P1196NOI2002银河英雄传说

#include<iostream>
#include<cstdio>
using namespace std;
int dis[30001];//每艘战舰到所在行的旗舰中间所隔战舰数
int len[30001];//每艘战舰所在行的战舰总数
int fa[30001];
int Find(int x)
{
    if(x==fa[x])
        return x;
    int t=Find(fa[x]);
    //先一步步跟到最终父节点
    dis[x]+=dis[fa[x]];//假定fa[fa[x]]为最终父节点,那么dis[fa[x]]就相当于直接连接到菊花图花心的边权,dis[x]+=dis[fa[x]]相当于将x从fa[x]直接链接到fa[fa[x]]
    fa[x]=t;//这时再把x直接链接到菊花图的花心
    return fa[x];
}
void Move(int x,int y)
{
    int fx=Find(x);
    int fy=Find(y);
    dis[fx]+=len[fy];
    len[fy]+=len[fx];
    fa[fx]=fy;
}
int ABS(int x)
{
    return max(x,-x);
}
char x[1];
int a1,a2;
int LINYIN()
{
    int T;
    scanf("%d",&T);
    for(int i=1;i<=30000;i++)
    {
        fa[i]=i;
        len[i]=1;
        dis[i]=0;
    }
    while(T--)
    {
        scanf("%s%d%d",x,&a1,&a2);
        if(x[0]==‘M‘)
        {
            Move(a1,a2);
        }
        else
        {
            if(Find(a1)!=Find(a2))
            {
                printf("%d\n",-1);
            }
            else
            {
                printf("%d\n",ABS(dis[a1]-dis[a2])-1);
            }
        }
    }
    return 0;
}
int LWH=LINYIN();
int main()
{
    ;
}

T2:洛谷P5092方块游戏\

#include<iostream>
#include<cstdio>
using namespace std;
int len[30001];
int dis[30001];
int fa[30001];
int Find(int x)
{
    if(x==fa[x])
        return x;
    int t=Find(fa[x]);
    dis[x]+=dis[fa[x]];
    fa[x]=t;
    return t;
}
void Move(int x,int y)
{
    int fx=Find(x);
    int fy=Find(y);
    fa[fy]=fx;
    dis[fy]=len[fx];
    len[fx]+=len[fy];
}
int n,a1,a2;
char k[1];
int LINYIN()
{
    scanf("%d",&n);
    for(int i=1;i<=30000;i++)
    {
        dis[i]=0;
        fa[i]=i;
        len[i]=1;
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%s",k);
        if(k[0]==‘M‘)
        {
            scanf("%d%d",&a1,&a2);
            Move(a1,a2);
        }
        else
        {
            scanf("%d",&a1);
            int fx=Find(a1);
            printf("%d\n",len[fx]-(dis[a1]+1));
            //cout<<len[fx]-(dis[a1]+1)<<endl;
        }
    }
    return 0;
}
int LWH=LINYIN();
int main()
{
    ;
}

完结撒花!!!

原文地址:https://www.cnblogs.com/XLINYIN/p/11706283.html

时间: 2024-08-05 20:30:59

蒟蒻林荫小复习——带权并查集的相关文章

蒟蒻林荫小复习——关于有限制区间元素查询的一些解法

如题:本文主要说明对于区间有限制查询的一些解法(其实就两种) 问题1:给定一个数列,要求查询区间L—R中所有大于等于Va小于等于Vb的元素和 解法: 1.线段树套权值线段树 第一维维护区间,第二维作为权值线段树,维护值域在A—B之间的元素之和 每次查询就从第一维拉到对应区间,然后用Va和Vb确定在权值线段树中的查询范围即可 2.分块 分块数组记为a,对每一个a块都开一个数组b,b数组将a块中元素拷贝后排序,新建c,对于每一个b都求前缀和 这样对于整块而言,用二分确定Va和Vb在b数组中的位置Ia

蒟蒻林荫小复习——K短路的A*解法

看标题都知道讲的是什么,但为什么特指是A*关于K短路的解法呢? 因为林荫不会其他的. 能看到这篇博客的估计也都知道K短路和A*分别是什么了吧,下面只介绍一下估价函数 由于林荫并没有经过学术训练,所以一下关于A*的理解均为感性,仅可作为OIer速成知识点时的一点资料, 切莫作为算法学术依据. 先思考一下,对于任意一条K短路,是不是均可写成由一部分最短路和一部分其他路径组成,而且这两部分路径还有且仅有一个公共点. 说明:1.任何一条路径都可以视为由其他到终点的路径和终点到终点的最短路组成 2.哪怕前

带权并查集复习-HDU3038

TT and FF are ... friends. Uh... very very good friends -________-b FF is a bad boy, he is always wooing TT to play the following game with him. This is a very humdrum game. To begin with, TT should write down a sequence of integers-_-!!(bored). Then

POJ 1984 Navigation Nightmare 【经典带权并查集】

任意门:http://poj.org/problem?id=1984 Navigation Nightmare Time Limit: 2000MS   Memory Limit: 30000K Total Submissions: 7783   Accepted: 2801 Case Time Limit: 1000MS Description Farmer John's pastoral neighborhood has N farms (2 <= N <= 40,000), usuall

并查集练习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

并查集2——带权并查集

路径压缩 前面的并查集的复杂度实际上有些极端情况会很慢.比如树的结构正好是一条链,那么最坏情况下,每次查询的复杂度达到了 O(n). 路径压缩 的思想是,我们只关心每个结点的父结点,而并不太关心树的真正的结构. 这样我们在一次查询的时候,可以把查询路径上的所有结点的 father[i] 都赋值成为根结点.只需要在我们之前的查询函数上面很小的改动. int get(int x) { if (father[x] == x) { // x 结点就是根结点 return x; } return fath

codeforces 687D Dividing Kingdom II 带权并查集(dsu)

题意:给你m条边,每条边有一个权值,每次询问只保留编号l到r的边,让你把这个图分成两部分 一个方案的耗费是当前符合条件的边的最大权值(符合条件的边指两段点都在一个部分),问你如何分,可以让耗费最小 分析:把当前l到r的边进行排序,从大到小,从大的开始不断加边,判断当前能否形成二分图,如果能形成二分图,继续加边 如果不能形成二分图,那当前边的权值就是最小耗费(是不是很眼熟) 思路很清晰,现在我们要解决的是如何判断可以形成二分图,有两种,一个是2染色当前图(肯定超时) 所以只剩一种方法,带权并查集

【HDOJ3047】Zjnu Stadium(带权并查集)

题意:浙江省第十二届大学生运动会在浙江师范大学举行,为此在浙师大建造了一座能容纳近万人的新体育场. 观众席每一行构成一个圆形,每个圆形由300个座位组成,对300个座位按照顺时针编号1到300,且可以认为有无数多行.现在比赛的组织者希望观众进入场地的顺序可以更加的有趣,在门票上并没有规定每个人的座位,而是与这个圈中某个人的相对位置,可以坐在任意一行. 门票上标示的形式如下:A B x 表示第B个人必须在A的顺时针方向x个位置(比如A坐在4号位子,x=2,则B必须坐在6号位子). 现在你就座位志愿

HDU 3038 How Many Answers Are Wrong 带权并查集

分析:这一题和HDU3047一样,都是带权并查集,求后输入和先输入的冲突个数 然后其实就是用并查集维护一棵树,小的作为大的祖先,然后这棵树每个节点到根的路径权值是相对根节点的距离 这样就可以维护距离限制,判断冲突 #include <cstdio> #include <cstring> #include <queue> #include <set> #include <map> #include <stack> #include &l