zoukankan      html  css  js  c++  java
  • 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
    ",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;
    }
  • 相关阅读:
    图-拓扑排序
    图-最短路径-Dijkstra及其变种
    【链表问题】打卡7:将单向链表按某值划分成左边小,中间相等,右边大的形式
    【链表问题】打卡5:环形单链表约瑟夫问题
    【链表问题】打卡6:三种方法带你优雅判断回文链表
    【链表问题】打卡4:如何优雅着反转单链表
    【链表问题】打卡3:删除单链表的中间节点
    【链表问题】打卡2:删除单链表的第 K个节点
    史上最全面试题汇总,没有之一,不接受反驳
    一些可以让你装逼的算法技巧总结
  • 原文地址:https://www.cnblogs.com/xiongtao/p/11282624.html
Copyright © 2011-2022 走看看