BZOJ 2208: [Jsoi2010]连通数(传递闭包模板题)

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2208

 

题意:定义一种连通数,一个点能通过边到达另一个点就是一个连通个数,规定点自己能到达自己,问每个点连通数之和是多少?

 

思路:首先避免有环,要先用tarjan缩点,并记录每个连通分量的点数。再反向建边,最后拓扑排序求连通分量之间是否可以达到。可以到达,答案加上两连通分量之积即可。

为什么是反向建边呢?

 

因为正向建边不能保证记录前面的连通分量通过  之后的连通分量  到达其他连通分量, 而反向是可以的。

例如:(正向建图,这里一个点代表一个连通分量)

可以通过拓扑排序,首先进入 1,可以知道  1连接 2  和3,但是马上1就出队列了,无法判定1连接4和5

反向建图:

反向后,可以通过,4,5连向3 记录,3连向4,5   在继续传递,3连向1时,就记录了4,5也连向1,就解决了 1 无法记录的问题。

这里用bitset容器   记录 连通分量之间是否连接。

代码:

#include<iostream> 
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<bitset>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=2100;
struct node{
    int u,v,nxt;
}e[maxn*maxn],e1[maxn*maxn];
int h[maxn],h1[maxn],dfn[maxn],low[maxn];
int st[maxn],belong[maxn],val[maxn],vis[maxn],in[maxn];
int cnt,cnt1,tot,top,num,scc,n,m;
bitset<maxn> lin[maxn];
void add(int u,int v)
{
    e[cnt].u=u;e[cnt].v=v;
    e[cnt].nxt=h[u];h[u]=cnt++;
}

void add1(int u,int v)
{
    e1[cnt1].u=u;e1[cnt1].v=v;
    e1[cnt1].nxt=h1[u];h1[u]=cnt1++;
}

void init()
{
    memset(h,-1,sizeof(h));
    memset(h1,-1,sizeof(h1));
    memset(dfn,0,sizeof(dfn));
    memset(belong,0,sizeof(belong));
    memset(val,0,sizeof(val));
    memset(vis,0,sizeof(vis));
    memset(in,0,sizeof(in));
    for(int i=1;i<=n;i++)
        lin[i][i]=1;//初始化,连通分量自己可以连接自己
    cnt=cnt1=num=top=tot=scc=0;
}

void tarjan(int u)//缩点 
{
    low[u]=dfn[u]=++tot;
    st[++top]=u;
    vis[u]=1;
    for(int i=h[u];i!=-1;i=e[i].nxt)
    {
        int v=e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        int t;
        num++;
        scc=0;
        do{
            t=st[top--];
            vis[t]=0;
            belong[t]=num;
            scc++;
        }while(t!=u);
        val[num]=scc;//记录每个连通分量点的个数 
    }
}

void topsort()//拓扑排序 
{
    int ans=0;
    queue<int> q;
    for(int i=1;i<=num;i++)
        if(in[i]==0)
            q.push(i);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=h1[u];i!=-1;i=e1[i].nxt)
        {
            int v=e1[i].v;
            in[v]--;
            lin[v]|=lin[u];//或运算,记录连通分量连接情况。
            //可以保证之前与u连接的连通分量,v也连接 
            if(in[v]==0)
                q.push(v);    
        }    
    }    
    for(int i=1;i<=num;i++)
        for(int j=1;j<=num;j++)
            if(lin[i][j])//两个连通分量相连
                ans+=val[i]*val[j];
    printf("%d\n",ans);
}

char s[maxn];
int main()
{
    scanf("%d",&n);
    init();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s);
        for(int j=0;j<n;j++)
        {
            if(s[j]=='1')
                add(i,j+1);
        }
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i);
    for(int i=0;i<cnt;i++)
    {
        if(belong[e[i].u]!=belong[e[i].v])
        {
            add1(belong[e[i].v],belong[e[i].u]);//反向建边 
            in[belong[e[i].u]]++;
        }    
    }
    topsort();
    return 0;
}

 

posted @ 2019-08-01 15:02  怀揣少年梦.#  阅读(344)  评论(0编辑  收藏  举报