[NOIp复习计划]:并查集

[bzoj 1370] [Baltic2003]Gang团伙
把一个点x拆成两个点x1,x2,与x1合并就代表是朋友,与x2合并就表示是敌人,这样处理的好处就是,假设y,z是x的敌人,那么把x2,与y,z分别合并时,y与x也就间接合并了,最后统计答案就好了。

#include<cstdio>
#include<algorithm>
using namespace std;
int fa[100010],a[100010];
int n,m,x,y,ans;
char ch[3];
inline int getfa(int x)
{return fa[x]==x?x:fa[x]=getfa(fa[x]);}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=2*n;i++)fa[i]=i;
    for (int i=1;i<=m;i++)
    {
        scanf("%s%d%d",ch+1,&x,&y);
        if (ch[1]=='F')fa[getfa(x)]=getfa(y);
        else
        {
            fa[getfa(x)]=getfa(n+y);
            fa[getfa(y)]=getfa(n+x);
        }
    }
    for(int i=1;i<=n;i++)a[i]=getfa(i);
    sort(a+1,a+n+1);
    ans=1;
    for(int i=2;i<=n;i++)if(a[i]!=a[i-1])ans++;
    printf("%d\n",ans);
    return 0;
}

[bzoj 1529][POI2005]ska Piggy banks
把钥匙的包含关系看成边,那么就形成了好多联通块,每个联通块中只要敲掉一个,每个就都可以访问到,维护联通块用并查集啦。

#include<bits/stdc++.h>
#define N 1000005
using namespace std;
int n,ans;
int fa[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=n;i++){
    int x;scanf("%d",&x);
    int p=find(i),q=find(x);
    if(p!=q)fa[q]=i;
    }
    for(int i=1;i<=n;i++)
        if(fa[i]==i)ans++;
    printf("%d",ans);
    return 0;
}


[bzoj 1116][POI2008]CLO
好吧先补一下基础知识,一个节点的入度和出度是在有向图中定义的,无向图并没有这种说法,于是树形结构是题意不满足的。
根据上面的定义我们可以知道,只要有个环就可以满足题意,可以脑补一下,对于每个极小环,我们可以按顺序构造有向边,顺时针或逆时针,当然环上每个点不一定只有一条边,对于那些边,我们可以选择把它设为方向向外指的有向边去连接一个树形结构,或是直接保留无向边(不对入度有贡献),去连接另一个环。
当然图也有不联通的现象。
这样题目就转化成了判断每个联通块中是否有环。
判断的方法,是在每个集合的根处维护一个bool值,代表该集合是否有环。
例如:合并x,y时,如果x,y在同一集合就把根的值设为true,不再一个集合,就把y并向x,根的值为x根的值|y根的值。

#include<cstdio>
const int MAXN = 100010;
int fa[MAXN];
bool tag[MAXN];
int getroot(int x){
    return fa[x]==x?x:fa[x]=getroot(fa[x]);
} 
void unite(int x,int y){
    int f_x=getroot(x);
    int f_y=getroot(y);
    if(f_x==f_y){
        tag[f_x]=1;
    }else{
        fa[f_x]=f_y;
        tag[f_y]|=tag[f_x];
    }
}

int main(){
    int n,m;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%d %d",&a,&b);
        unite(a,b);
    }
    for(int i=1;i<=n;i++){
        if(fa[i]==i && tag[i]==0){
            puts("NIE");
            return 0;
        }
    }
    puts("TAK");
    return 0;
}

[bzoj 1015][JSOI2008]星球大战starwar
在线的方法不好想。感觉按秩合并并查集可做
考虑离线做法,一开就把所有路炸掉,然后倒着做。
这就变成了,每次合并两个集合,并维护联通块的个数,就可以随便搞了。
今年1月写的代码风格怎么跟现在差那么多

#include<cstdio>
#define rep(i,a,b) for(i=a;i<=b;i++)
#define dep(i,a,b) for(i=a;i>=b;i--)
#define maxn  200010
int fa[maxn<<1],h[maxn<<1],nx[maxn<<2],to[maxn<<2],tm[maxn<<1],ans[maxn<<1],mk[maxn<<1],len,m,n,cnt,cntOfTm;
int getroot(int x){
    return fa[x]==x?x:fa[x]=getroot(fa[x]);
}
void Find(int &ret,int nd){
    ret=getroot(nd);
}
#define Merge(aaa,bbb) {int ra,rb;Find(ra,aaa);Find(rb,bbb);if(ra!=rb)cnt--;fa[rb]=ra;}
#define Init(){int ii;rep(ii,1,n)fa[ii]=ii;}
#define AddEdge(a,b){len++;nx[len]=h[a];to[len]=b;h[a]=len;}

int main()
{
    int i,j,k,a,b;
    scanf("%d%d",&n,&m);
    Init();
    rep(i,1,m)
    {
        scanf("%d%d",&a,&b);a++;b++;
        AddEdge(a,b);AddEdge(b,a);
    }
    scanf("%d",&cntOfTm);
    rep(i,1,cntOfTm){
        scanf("%d",&a);a++;tm[a]=1;mk[i]=a;
    }
    rep(i,1,n)
    {

        if(tm[i]!=0) continue;
        cnt++;
        for(j=h[i];j;j=nx[j])
        {
            if(tm[to[j]]==0)
            {
                Merge(i,to[j]);
            }
        }
    }
    ans[cntOfTm]=cnt;
    dep(i,cntOfTm,1)
    {
        tm[mk[i]]=0;cnt++;
        for(j=h[mk[i]];j;j=nx[j])
        {

            if(tm[to[j]]==0)
            {
                Merge(to[j],mk[i]);

            }
        }
        ans[i-1]=cnt;
    }
    rep(i,0,cntOfTm)
        printf("%d\n",ans[i]);
    return 0;
}

[bzoj 1854][Scoi2010]游戏
是个裸的二分图模型
我们把属性看成点,把装备看成边,这样对于每个联通块,假设有n个点,如果有环,那么n个都可以用,而如果没有环,那就只能有n-1个。
下面是令人称奇的微操。并没有
vis[i]i
xyx<y
如果它们不属于一个集合,那么比较x的根和y的根的大小,把小的并向大的,然后把小的根的vis设为true。
这是因为我们要维护每个集合的根是该集合内元素的极大值,这样方便更新vis的值。
而如果它们在一个集合里,那么就把根的vis设为true就好了,这是因为这个联通块里有环,所以最大值就可以取了。
最后从1-100000看看vis,更新答案就好了。

#include<cstdio>
#define rep(i,a,b) for(i=a;i<=b;i++)
#define dep(i,a,b) for(i=a;i>=b;i--)
const int MAXN = 1000005;
int n,fa[MAXN];
bool right[MAXN];
int GetRoot(int x){return fa[x]==x?x:fa[x]=GetRoot(fa[x]);}
void solve(int x,int y){
    int Fx=GetRoot(x),Fy=GetRoot(y);
    if(Fx==Fy){right[Fy]|=1;}
    else{if(Fx>Fy){int t=Fx;Fx=Fy;Fy=t;}fa[Fx]=Fy;right[Fx]|=1;}
}
int main(){
    int i,a,b;
    scanf("%d",&n);
    rep(i,1,100006)fa[i]=i;
    rep(i,1,n){
        scanf("%d%d",&a,&b);
        solve(a,b);
    }
    rep(i,1,100005){
        if(!right[i]){
            printf("%d",i-1);
            return 0;
        }
    }
    return 0;
}


[bzoj 1202][HNOI2005]狡猾的商人
把每个月看成点,因为要维护值,考虑带权并查集,每个节点多维护一个值表示与父节点值的差值。
然后乱搞就行了233.

#include<cstdio>
#define rep(i,a,b) for(i=a;i<=b;i++)
#define dep(i,a,b) for(i=a;i>=b;i--)
#define MAXN 120
int fa[MAXN],val[MAXN],t,n,m;
void ReSet(){int i;rep(i,0,MAXN-1)fa[i]=i,val[i]=0;}
int GetRoot(int x){if(fa[x]==x)return fa[x];int k=GetRoot(fa[x]);val[x]+=val[fa[x]];fa[x]=k;return k;}
int main(){
    int i,a,b,c;
    bool flag=0;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        ReSet();
        flag=0;
        rep(i,1,m){
            scanf("%d%d%d",&a,&b,&c);a--;
            int Fa=GetRoot(a),Fb=GetRoot(b);

            if(Fa==Fb){
                if(val[b]-val[a]!=c && flag==0){
                    puts("false");
                    flag=1;
                }
            }else{
                fa[Fa]=Fb;
                val[Fa]=val[b]-val[a]-c;
            }
        }
        if(!flag)puts("true");
    }
    return 0;
}

to be continued……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值