【BZOJ2208/Jsoi2010】连通数

                                2208: [Jsoi2010]连通数

                                                   Time Limit: 20 Sec  Memory Limit: 512 MB
                                                            Submit: 4160  Solved: 1794

Description

Input

输入数据第一行是图顶点的数量,一个正整数N。 接下来N行,每行N个字符。第i行第j列的1表示顶点i到j有边,0则表示无边。

Output

输出一行一个整数,表示该图的连通数。

Sample Input

3
010
001
100

Sample Output

9

HINT

对于100%的数据,N不超过2000。

 

解析:

       这道题数据是真的水而且做法也很多。。。

       最先看到这道题感觉数据范围挺小的,感觉暴力(实际上最坏O(N^3))没问题,结果还真能过。。。

       但是我们不能仅仅满足于AC对吧,所以思考一下正解。

       首先强连通缩点,重新简图,于是答案就为:

       ans=\sum_{i=1}^{n}\sum_{j=1}^{n}sum[i]*sum[j]

       前提条件是连通块i能到达于连通块j

       如何算出连通块之间的连通性?直接用Floyd传递闭包,但是最好用bitset压位能跑得更快。

      当然也有人缩完点后用拓扑序递推复杂度应该更优秀一些,还有人不缩点直接用bitset压位跑Floyd也能过而且速度也不慢。。。

 

搜索(最慢但最好想):

#include <bits/stdc++.h>
using namespace std;
 
const int Max=2010;
int n,m,ans,size;
int first[Max],vis[Max];
char ch[Max];
struct shu{int to,next;};
shu edge[Max*Max];
 
inline int get_int()
{
    int x=0,f=1;
    char c;
    for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
    if(c=='-') f=-1,c=getchar();
    for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
    return x*f;
}
 
inline void build(int x,int y)
{
    edge[++size].next=first[x];
    first[x]=size;
    edge[size].to=y;
}
 
inline void dfs(int point)
{
    if(!vis[point]) ans++,vis[point]=1;
    for(int u=first[point];u;u=edge[u].next)
    {
      int to=edge[u].to;
      if(vis[to]) continue;
      dfs(to);
    }
}
 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
      scanf("%s",ch+1);
      for(int j=1;j<=n;j++) if(ch[j]=='1') build(i,j);
    }
    for(int i=1;i<=n;i++)
    {
      dfs(i);
      memset(vis,0,sizeof(vis));
    }
    cout<<ans<<"\n";
    return 0;
}

直接bitset压位Floyd(最短速度适中):

//次代码源自https://blog.csdn.net/linkfqy/article/details/75578669
#include<cstdio>
#include<bitset>
using namespace std;
const int maxn=2005;
int n,ans;
char s[maxn];
bitset<maxn> f[maxn];
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        scanf("%s",s);
        for (int j=1;j<=n;j++)
         if (s[j-1]=='1'||i==j) f[i][j]=1;
    }
    for (int i=1;i<=n;i++)
     for (int j=1;j<=n;j++)
      if (f[j].test(i)) f[j]|=f[i];
    for (int i=1;i<=n;i++) ans+=f[i].count();
    printf("%d",ans);
    return 0;
}

缩点+bitset压位Floyd(速度较快):

#include <bits/stdc++.h>
using namespace std;
 
const int Max=2005;
int n,m,size,cnt,tot,Index,ans;
int first[Max],low[Max],num[Max],p[Max],vis[Max],sum[Max],father[Max];
char ch[Max];
struct shu{int to,next;};
shu edge[Max*Max];
bitset<2005>f[Max];
 
inline int get_int()
{
    int x=0,f=1;
    char c;
    for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
    if(c=='-') f=-1,c=getchar();
    for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
    return x*f;
}
 
inline void build(int x,int y)
{
    edge[++size].next=first[x];
    first[x]=size;
    edge[size].to=y;
}
 
inline void tarjan(int point)
{
    num[point]=low[point]=++Index;
    p[++tot]=point,vis[point]=1;
    for(int u=first[point];u;u=edge[u].next)
    {
      int to=edge[u].to;
      if(!num[to]) tarjan(to),low[point]=min(low[point],low[to]);
      else if(vis[to]) low[point]=min(num[to],low[point]);
    }
    if(low[point]==num[point])
    {
      cnt++;
      while(1)
      {
        int x=p[tot--];
        father[x]=cnt,vis[x]=0,sum[cnt]++;
        if(x==point) break;
      }
    }
}                  
 
inline void solve()
{
    size=0;
    for(int i=1;i<=cnt;i++) f[i][i]=1;
    for(int i=1;i<=n;i++)
      for(int u=first[i];u;u=edge[u].next)
         f[father[i]][father[edge[u].to]]=1;
    for(int i=1;i<=cnt;i++)
      for(int j=1;j<=cnt;j++)
        if(f[i][j]) f[i]|=f[j];
    for(int i=1;i<=cnt;i++)
      for(int j=1;j<=cnt;j++)
        if(f[i][j]) ans+=sum[i]*sum[j];
}
 
int main()
{
    n=get_int();
    for(int i=1;i<=n;i++)
    {
      scanf("%s",ch+1);
      for(int j=1;j<=n;j++) if(ch[j]=='1') build(i,j);
    }
    for(int i=1;i<=n;i++) if(!num[i]) tarjan(i);
    solve();
    cout<<ans<<"\n";
    return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值