并查集

感谢cyanigence-oi大佬(Orz)的题单

模板 

并查集 模板题

预处理过程:void pre(),赋值数组a

假设自己就是祖先,则用for循环,来储存每个父亲节点的值

void pre()
{
    for(int i=1;i<=n;i++) a[i]=i;
}

查询过程:int find(int k)

用递归的形式一层一层的探索自己的祖先,根节点表示的就是祖先,判断两个人是不是同一个祖先,只要查询各自的根节点是否相同即可

int find(int k)
{
    if(a[k]==k) return k;
    else return a[k]=find(a[k]);
}

合并过程:void merge(int u,int v)

void merge(int u,int v)
{
    a[find(u)]=find(v);
}

按秩合并

 

P1551 亲戚

#include <bits/stdc++.h>
using namespace std ;
typedef long long ll;
const int maxn = 2e5 + 10;
typedef long long ll;
ll n,m,p;
int a[maxn];
void pre()    //预处理
{
    for(ll i=1;i<=n;i++)
    {
        a[i]=i;
    }
}
ll find(ll k)    //查询+路径压缩
{
    if(a[k]==k) return k;
    else return a[k]=find(a[k]);
}
void merge1(ll u,ll v)    //合并路径
{
    a[find(u)]=find(v);
}
int main()
{
    scanf("%lld%lld%lld",&n,&m,&p);
    pre();
    ll x,y;
    for(ll i=1;i<=m;i++)
    {
        scanf("%lld%lld",&x,&y);
        merge1(x,y);
    }
    for(ll i=1;i<=p;i++)
    {
        scanf("%lld%lld",&x,&y);
        if(find(x)==find(y))
            printf("Yes\n");
        else
            printf("No\n");
    }
}

 

P2814 家谱

  用标准库的map

#include <bits/stdc++.h>
using namespace std;
map<string,string>p;
string find(string x)
{
    if(p[x]==x) return x;
    else return p[x]=find(p[x]);
}
int main()
{
    string sss,ss;
    char c;
    while(1)
    {
        scanf("%c",&c);
        if(c=='$')
            break;
        else if(c=='#')
        {
            cin>>ss;
            if(p[ss]=="")
                p[ss]=ss;
        }
        else if(c=='?')
        {
            cin>>sss;
            cout<<sss<<" "<<find(sss)<<endl;
        }
        else if(c=='+')
        {
            cin>>sss;
            p[sss]=ss;
        }
    }
}

 

P1536 村村通

 找查两两村是否联通,即是否为同一个祖先,变量s作为记录连通块中的边数,因为并不是每个村和其他的村庄都是联通的,有可能存在鼓励的村庄。

#include <bits/stdc++.h>
using namespace std ;
typedef long long ll;
const ll maxn=2e5+10;;
ll n,m,a[maxn];
inline void pre()
{
    for(ll i=1; i<=n; i++)
    {
        a[i]=i;
    }
}
inline ll find(ll k)
{
    if(a[k]!=k) a[k]=find(a[k]);
    return a[k];

}
void merge(ll u,ll v)
{
    a[find(u)]=find(v);
}
int main()
{
    while(1)
    {
        ll f;
        scanf("%lld",&f);
        n=f;
        if(f==0) return 0;
        pre();
        scanf("%lld",&m);
        ll s=0;
        for(ll i=1; i<=m; i++)
        {
            ll x,y;
            scanf("%lld%lld",&x,&y);
            merge(x,y);
        }
        for(ll i=1; i<=n; i++)
        {
            if(a[i]==i)
            {
                s++;
            }
        }
        cout<<s-1<<endl;
    }
}

 

P1396 营救

 创建结构体,存入u,v,w,对拥挤度w进行升序排序;

并查集,当查询到s和t连通时,输出当前的最大拥挤度,由于之前已经有过升序排序的操作,这时候的最大拥挤度是最小的。

#include <bits/stdc++.h>
using namespace std ;
typedef long long ll;
const ll maxn=2e5+10;
struct node
{
    ll u,v,w;
}way[maxn];
inline bool cmp(node a,node b)
{
    return a.w<b.w;
}
ll n,m,s,t;
ll a[maxn];
void pre()
{
    for(ll i=0;i<maxn;i++)
    {
        a[i]=i;
    }
}
inline ll find(ll k)
{
    if(a[k]!=k)
    {
        a[k]=find(a[k]);
    }
    return a[k];
}
inline void merge(ll uu,ll vv)
{
    ll uuu=find(uu),vvv=find(vv);
    if(a[uuu]!=vvv)
        a[uuu]=vvv;
}
int main()
{
    scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
    pre();
    for(ll i=1;i<=m;i++)
    {
        scanf("%lld%lld%lld",&way[i].u,&way[i].v,&way[i].w);
    }
    sort(way+1,way+1+m,cmp);
    for(ll i=1;i<=m;i++)
    {
        merge(way[i].u,way[i].v);
        if(find(a[s])==find(a[t]))
        {
            printf("%lld\n",way[i].w);
            break;
        }
    }
}

P1621 集合

 

P4185 [USACO18JAN]MooTube

 

P1197 [JSOI2008]星球大战

 

bzoj2054疯狂的馒头

 

P2294 [HNOI2005]狡猾的商人

 

P1892 [BOI2003]团伙

 

P1455 搭配购买

并查集+01背包

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, m, w;
ll a[100005], c[100005], d[100005],dp[100005];
void init()
{
    for (ll i = 1; i <= 100004; i++)
    {
        a[i] = i;
    }
}
ll find(ll k)
{
    if (a[k] == k)
    {
        return k;
    }
    else
    {
        return a[k] = find(a[k]);
    }
}
void merge(ll u, ll v)
{
    a[find(u)] = find(v);
}
int main()
{
    init();
    scanf("%lld %lld %lld", &n, &m, &w);
    for (ll i = 1; i <= n; i++)
    {
        scanf("%lld %lld", &c[i], &d[i]);
    }
    for (ll i = 1; i <= m; i++)
    {
        ll x, y;
        scanf("%lld %lld", &x, &y);
        merge(x, y);
    }
    ll s = 0;
    for (ll i = 1; i <= n; i++)
    {
        if (a[i] != i)
        {    
            d[find(i)] += d[i];
            d[i] = 0;
            c[find(i)] += c[i];
            c[i] = 0;
        }
    }
    for (ll i = 1; i <= n; i++)
    {
        for (ll j = w; j >= c[i]; j--)
        {
            dp[j] = max(dp[j], dp[j - c[i]] + d[i]);
        }
    }
    printf("%lld\n", dp[w]);
}

 带权并查集

公司竞争

虽然题目给了2秒,但是也不可以一遍一遍的查询,大佬何哥给了一种思路,即在每次认祖宗的时候,把儿子的势力值加到父亲的势力值中,时间可以大大缩短,给1秒都够

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, m, k;
ll a[100005], t[100005];
void init()
{
    for (ll i = 0; i < 100005; i++)
    {
        a[i] = i;
    }
}
ll find(ll k)
{
    if (a[k] == k)
    {
        return k;
    }
    else
    {
        return a[k] = find(a[k]);
    }
}
void merge(ll u, ll v)
{
    a[find(u)] = find(v);
}
int main()
{
    init();
    scanf("%lld %lld",&n,&m);
    for(ll i=1;i<=n;i++)
    {
        scanf(" %lld",t+i);
    }
    for(ll i=1;i<=m;i++)
    {
        ll f,x,y;
        scanf("%lld",&f);
        if(f==1)
        {
            scanf("%lld %lld",&x,&y);
            ll xx=find(x);
            ll yy=find(y);
            if(xx!=yy)
            {
                a[yy]=xx;
                t[xx]+=t[yy];
            }
        }
        else if(f==2)
        {
            scanf("%lld",&x);
            printf("%lld\n",t[find(x)]);
        }
    }
}

 

 

Interesting Computer Game   2020牛客暑期多校训练营(第八场)

#include <bits/stdc++.h>
#define T int t ;cin >> t;while(t--)
using namespace std ;
typedef long long ll;
const int maxn = 2e5 + 10;
ll vis[maxn],a[maxn],b[maxn],c[maxn],pre[maxn];
//vis数组表示的是当前的节点是否被访问过
//pre数组表示的是合并路径
inline ll find(ll x)
{
    return (x==pre[x]) ? x:pre[x]=find(pre[x]);
}
inline void merge(ll u,ll v)
{
    ll x=find(u),y=find(v);
    if(x==y)
    {
        vis[x]=1;
        return;
    }
    pre[x]=y;//表示x,y的祖宗合并,即两者为同一祖先
    if(vis[x])vis[y]=1;//该节点表示已被访问
}
int main()
{
    ll total=0;//用于输出Case情况的个数
    T
    {
        total++;
        ll n;
        ll tot=0;//tot表示存入数的个数
        scanf("%lld",&n);
        for(ll i=1; i<=n; i++)
        {
            scanf("%lld%lld",&a[i],&b[i]);
            c[++tot]=a[i];//c数组用于存放每个数字,方便接下去排序和去重,假设a和b的值完全不一样,那么c数组的大小需要两倍空间,即2e5+5
            c[++tot]=b[i];
        }
        for(ll i=0; i<=maxn; i++)//初始化操作
        {
            vis[i]=0;
            pre[i]=i;
        }
        sort(c+1,c+tot+1);//升序排序,便于接下来的去重
        int cnt=unique(c+1,c+tot+1)-(c+1);//去重,方便编号
        for(ll i=1; i<=n; i++)
        {
            a[i]=lower_bound(c+1,c+tot+1,a[i])-c;
//从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,
//找到返回该数字的地址,不存在则返回end。
//通过返回的地址减去起始地址begin,得到找到数字在数组中的下标,下标即树的度数
            b[i]=lower_bound(c+1,c+tot+1,b[i])-c;
            merge(a[i],b[i]);//用下标代替实际数字,防止数组存不下
        }
        int ans=tot;
        for(ll i=1; i<=tot; i++)
        {
            if(pre[i]==i&&!vis[i])ans--;//根据《离散数学》相关知识,如果非连通块则ans减一
        }
        cout<<"Case #"<<total<<": "<<ans<<endl;
    }
}

 P3958 奶酪

 

 被scy修改过的删边问题

贪心题

只要保证连通,即只要从树根节点到达叶子节点,变成初级通路(每个点只经过一次,边数=点数-1),不必构成回路,结果等于原始总边数(m)-初级通路(n-1)

#include <bits/stdc++.h>
using namespace std ;
int main()
{
    int a,b;
    cin>>a>>b;
    int bb=b;
    while(b--){int x;cin>>x>>x;}
    printf("%d\n",bb-a+1);
}

 

 家族

查找连通块的个数,如果a[i]==i,则s++,s变量统计的就是连通块的个数。

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll maxn=1e5+10;
ll a[maxn];
void pre()
{
    for(ll i=1;i<maxn;i++)
    {
        a[i]=i;
    }
}
inline ll find(ll k)
{
    if(a[k]==k)
    {
        return a[k];
    }
    else
    {
        return a[k]=find(a[k]);
    }
}
inline void merge(ll u,ll v)
{
    a[find(u)]=find(v);
}
ll n,m;
int main()
{
    scanf("%lld%lld",&n,&m);
    pre();
    ll x,y;
    for(ll i=1;i<=m;i++)
    {
        scanf("%lld%lld",&x,&y);
        merge(x,y);
    }
    ll s=0;
    for(ll i=1;i<=n;i++)
    {
        if(a[i]==i)
            s++;
    }
    cout<<s<<endl;
}

 

posted @ 2020-08-02 16:35  Drophair  阅读(173)  评论(0编辑  收藏  举报