zoukankan      html  css  js  c++  java
  • HDU 3639 Hawk-and-Chicken (强连通缩点+DFS)

    <题目链接>

    题目大意:

    有一群孩子正在玩老鹰抓小鸡,由于想当老鹰的人不少,孩子们通过投票的方式产生,但是投票有这么一条规则:投票具有传递性,A支持B,B支持C,那么C获得2票(A.B共两票),输出最多能获得的票数是多少张和获得最多票数的人是谁?(如果有多个人获得的票数都是最多的,就将他们全部输出)。

    解题分析:

    不难看出,同一连通分量的所有点得到的票数肯定是相同的,所以我们先将原图进行Tarjan缩点。对于缩完点后的图,我们发现,票数最多的人一定在出度为0的"点"中,因为如果票数最多的点不是出度为0的"点",那么它所到达的那个"点"的票数一定大于当前"点"的票数,这与当前"点"票数最多相矛盾。然后考虑对入度为0的"点"的票数统计,我们可以在缩点后进行重新构图,相互连通的"点"之间建立反边(投票方向相反),这样方便DFS统计当前"点"的票数。需要注意的是,当前连通分量中所有的点在当前连通分量中获得的票数为num[now]-1(因为要除去自己),然后再加上以这个点为起点,能够到达所有连通分量的点数,即为这个点能够得到的票数。

     CODE

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <set>
    #include <stack>
    using namespace std;
    typedef long long ll;
    #define cls(s,h) memset(s,h,sizeof s)
    const int maxn = 1e5 + 7;
    int n, m,cnt, cnt1, scc/*强连通分量*/,sum;
    int tot;
    vector<int>g[maxn];
    
    struct edge
    {
        int to,from,nxt;
    } e[maxn << 1],edge1[maxn << 1];
    
    int head[maxn];
    void add_edge(int u, int v )
    {
        e[tot].from = u ;
        e[tot].to = v;
        e[tot].nxt = head[u];
        head[u] = tot++;
    }
    
    int head1[maxn];
    void add_edge_two(int u,int v)       //添加缩点后的反向边
    {
        edge1[cnt1].to=v,edge1[cnt1].nxt=head1[u];
        head1[u]=cnt1++;
    }
    
    int instk[maxn],belong[maxn];
    int dfn[maxn],low[maxn],idx;
    stack<int> stk;
    int ans[maxn];
    
    void tanjan(int u )
    {
        dfn[u] = low[u] = ++ idx;
        stk.push(u);
        instk[u] = 1;
        for(int i  = head[u]; ~i ; i = e[i].nxt)
        {
            int v = e[i].to;
            if(!dfn[v])
            {
                tanjan(v);
                low[u] = min(low[u],low[v]);
            }
            else if(instk[v]) low[u] = min(low[u],dfn[v]);
        }
        if(dfn[u] == low[u])
        {
            scc++;
            while(1)
            {
                int v = stk.top();
                stk.pop();
                instk[v] = 0;
                // v 点属于 scc这个强连通分量
                belong[v] = scc;
                //强连通分量点集
                g[scc].push_back(v);
                if(v == u)break;
            }
        }
    }
    
    int vis[maxn],num[maxn];
    
    void dfs(int u)    //统计以u连通分量为起点能够到达所有的连通分量的点数之和
    {
        vis[u]=1;
        sum+=num[u];
        for(int i=head1[u]; ~i; i=edge1[i].nxt)
        {
            int v=edge1[i].to;
            if(!vis[v])dfs(v);
        }
    }
    int out[maxn];
    void init()
    {
        for(int i = 0; i <= n ; i ++) g[i].clear();
        cls(num,0);
        cls(ans,0);
        cls(low,0);
        cls(instk,0);
        cls(belong,0);
        cls(out,0);
        cls(head,-1);
        cls(dfn,0);
        cls(head1,-1);
        cnt1 = scc = sum = tot  = 0;
    }
    
    int main(int argc, char const *argv[])
    {
        int T;
        scanf("%d",&T);
        int cases = 1;
        while(T--)
        {
    
            scanf("%d %d",&n,&m);
            int u, v;
            init();
            for(int i = 1; i <= m ; i ++)
            {
                scanf("%d %d",&u,&v);
                u ++;
                v ++;
                add_edge(u,v);
            }
            for(int i = 1; i <= n; i ++)
            {
                if(!dfn[i]) tanjan(i);
            }
            //记录每个强连通分量的点集数
            for(int i = 1; i <= n ; i ++) num[belong[i]] ++;
            for(int u = 1; u <= n ; u ++)
                for(int i = head[u]; ~i ; i = e[i].nxt )
                {
                    int v = e[i].to;
                    int tmp1 = belong[u],tmp2 = belong[v];
                    //缩点,不同则合并
                    if(tmp2 != tmp1)
                    {
                        //反向建图
                        add_edge_two(tmp2,tmp1);
                        out[tmp1]++;//记录出度
                    }
                }
            int mx = -1;
            for(int i = 1; i <= scc; i++)if(!out[i])
                {
                    cls(vis,0);
                    sum =  0;
                    dfs(i);//求得到的票数
                    ans[i] = sum - 1;//减去自己
                    mx = max(ans[i],mx);
                }
            bool flag = 0;
            printf("Case %d: %d
    ",cases ++,mx);
            for(int i = 1; i <= n ; i ++)
            {
                if(ans[belong[i]] == mx)
                {
                    //属于这个强连通分量的点集
                    if(!flag)
                        printf("%d",i - 1 );
                    else printf(" %d", i - 1);
                    flag = 1;
                }
            }
            puts("");
        }
    
        return 0;
    }
  • 相关阅读:
    SQL SERVER常用取重复记录的SQL语句
    订单号生成
    SQL Server 2005中返回修改后的数据
    一条sql语句,要修改一个字段的俩个值,比如把字段sex中的男改为女,女改为男
    C#输入法全半角转换
    dataGridView1 筛选
    C#(WIN FORM)两个窗体间LISTVIEW值的修改
    ms sql server 2005数据库日志文件过大,需要清除或者清空
    SQL 判断表是否存在
    C#将数据写入记事本并且从记事本中读出
  • 原文地址:https://www.cnblogs.com/DWVictor/p/11333716.html
Copyright © 2011-2022 走看看