bzoj1529: [POI2005]ska Piggy banks(tarjan||并查集)

该博客探讨了如何解决bzoj1529问题,即计算联通块的数量。文章提到,尽管可以使用Tarjan算法进行缩点来统计,但在面对内存限制时可能会遇到问题。因此,作者提出了使用并查集作为替代方法来有效维护联通块。在最终统计答案时,通过计算点的所属联通块数量或者不变的点数(两者等价)来得出结果。实现中,使用并查集的方法在效率上表现出色,分别用时1720ms和1940ms。

题目大意:求联通块的数量
显然可以想到tarjan缩点之后统计出度为0的点的数量
但这题会MLE

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e6+10;
int n,len=0,tot=0,cnt=0,scc=0,top=0,ans=0,last[N],head[N],
dfn[N],low[N],ins[N],v[N],bl[N],s[N],fa[N];
struct Edge{int to,next;Edge(int to=0,int next=0):to(to),next(next){}}e[N<<1],E[N<<1];
void add_edge(int u,int v){e[++len]=Edge(v,last[u]);last[u]=len;}
void add_Edge(int u,int v){E[++tot]=Edge(v,head[u]);head[u]=tot;}
void tarjan(int x)
{
    int now=0;
    dfn[x]=low[x]=++cnt;
    s[++top]=x;ins[x]=1;
    for(int i=last[x];i;i=e[i].next) {
        if(!dfn[e[i].to]){
            tarjan(e[i].to);
            low[x]=min(low[x],low[e[i].to]);
        }else if(ins[e[i].to])low[x]=min(low[x],dfn[e[i].to]);
    }
    if(dfn[x]==low[x]) {
        ++scc;
        while(now!=x) {
            now=s[top--];ins[now]=0;
            bl[now]=scc;
            v[scc]++;
        }
    }
}
void rebuild()
{
    tot=0;
    for(int i=1;i<=n;i++) 
        for(int j=last[i];j;j=e[i].next) 
            if(bl[i]!=bl[e[i].to])
                add_Edge(bl[i],bl[e[j].to]);
}
void solve()
{
    for(int i=1;i<=scc;i++) {
        if(!head[i]) ans++;
    }
}
int main()
{
    scanf("%d",&n);
    for(int id,i=1;i<=n;i++) {
        scanf("%d",&id);
        add_edge(id,i);
    }   
    fo(i,1,n)if(!dfn[i])tarjan(i);
    rebuild();
    solve();
    printf("%d\n",ans-1);
    return 0;
}

对于联通块还有一种数据结构可以维护:并查集
最后统计答案时,就是每个点的所属有多少个不同编号,或者是有多少点的编号不变(两者是等价的,但后者快一点)(1720ms,1940ms)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e6+10;
int fa[N],n,len=0,vis[N],ans=0;
struct Edge{int from,to;Edge(int from=0,int to=0):from(from),to(to){}}e[N];
void add_edge(int u,int v){e[++len]=Edge(u,v);}
int Fd(int x){return x==fa[x]?fa[x]:fa[x]=Fd(fa[x]);}
void solve()
{
    fo(i,1,n) fa[i]=i;
    for(int i=1;i<=n;i++) {
        int idx=Fd(e[i].from),idy=Fd(e[i].to);
        if(idx==idy)continue;
        fa[idx]=idy;
    }
    fo(i,1,n) if(!vis[Fd(i)]){ans++;vis[Fd(i)]=1;}
//或者    fo(i,1,n) if(fa[i]==i) ans++;
}
int main()
{
    scanf("%d",&n);
    for(int id,i=1;i<=n;i++) {
        scanf("%d",&id);
        add_edge(id,i);
    }
    solve();
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值