HDU 3639 Hawk-and-Chicken(强连通分量+缩点)
http://acm.hdu.edu.cn/showproblem.php?pid=3639
题意:
给你一个有向图,如果从u点能到达v点,那么说u是v的粉丝,现在要你按序输出那些粉丝数目最多的点编号.
分析:
假设该图是一个强连通图,那么任一点都有n-1个粉丝(即n-1个点能到达它).所以我们把该图缩点变成一个新的DAG图.
结论:原图中具有最多粉丝的点一定在新图的那些出度为0的点所代表的分量中.
证明:假设u节点粉丝最多且它所属的分量出度不为0,那么u节点一定是某个节点v的粉丝,所以v的粉丝必然包含了u的所有粉丝加上u本身.所以v的粉丝必然多余u.由此矛盾.
下面的问题是如何找新DAG图的每个节点(所代表分量中的原节点)的最大粉丝数? 该粉丝数=本连通分量的点数-1+本连通分量能通过ß这种边逆向走到的所有分量的点数和. 所以我们直接建立缩点树的逆图DAG即可,如果u->v表示u分量将获得v分量的所有节点作为粉丝.所以我们只需要对那几个入度为0的点做DFS即可.
每次DFS到一个新节点,该点所代表的分量节点数就都加到sum上去,表示新加了很多粉丝.最后找最大粉丝值的分量点输出即可.
AC代码:
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> #include<stack> using namespace std; const int maxn= 5000+10; int n,m; vector<int> G[maxn], G2[maxn]; stack<int> S; int dfs_clock, scc_cnt; int pre[maxn],sccno[maxn],low[maxn]; int num[maxn];//表每个强连通分量各含多少点 int in[maxn];//新DAG的逆图中点的入度 int fan[maxn];//表新DAG中第i个点(分量)有多少粉丝 void dfs(int u) { pre[u]=low[u]=++dfs_clock; S.push(u); for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(!pre[v]) { dfs(v); low[u]=min(low[u],low[v]); } else if(!sccno[v]) low[u]=min(low[u],pre[v]); } if(low[u]==pre[u]) { scc_cnt++; num[scc_cnt]=0; while(true) { int x=S.top(); S.pop(); sccno[x]=scc_cnt; num[scc_cnt]++; if(x==u) break; } } } void find_scc(int n) { dfs_clock=scc_cnt=0; memset(pre,0,sizeof(pre)); memset(sccno,0,sizeof(sccno)); for(int i=0;i<n;i++) if(!pre[i]) dfs(i); } bool vis[maxn]; int dfs2(int u) { vis[u]=true; int sum=0; for(int i=0;i<G2[u].size();i++) { int v=G2[u][i]; if(!vis[v]) sum+=num[v]+dfs2(v); //WA-> vis[i] num[i] dfs2(i) } return sum; } int main() { int T; scanf("%d",&T); for(int kase=1;kase<=T;kase++) { scanf("%d%d",&n,&m); for(int i=0;i<n;i++) G[i].clear(); while(m--) { int u,v; scanf("%d%d",&u,&v); G[u].push_back(v); } find_scc(n); memset(in,0,sizeof(in)); for(int i=1;i<=scc_cnt;i++) G2[i].clear(); for(int u=0;u<n;u++) for(int i=0;i<G[u].size();i++) { int v=G[u][i]; int x=sccno[u], y=sccno[v]; if(x!=y) { in[x]++; G2[y].push_back(x);//建立DAG的逆图 } } memset(fan,0,sizeof(fan)); int max_f=-1; for(int i=1;i<=scc_cnt;i++) if(!in[i]) { memset(vis,0,sizeof(vis)); //WA-> vis数组只在外面初始化1次 fan[i] = num[i]-1+dfs2(i); max_f=max(fan[i],max_f); } bool win[maxn]; memset(win,0,sizeof(win)); for(int i=0;i<n;i++) if(fan[sccno[i]]==max_f) win[i]=true; printf("Case %d: %d\n",kase,max_f); bool first=true; for(int i=0;i<n;i++)if(win[i]) { if(first) printf("%d",i), first=false; else printf(" %d",i); } puts(""); } return 0; }