<题目链接>
题目大意:
有一群孩子正在玩老鹰抓小鸡,由于想当老鹰的人不少,孩子们通过投票的方式产生,但是投票有这么一条规则:投票具有传递性,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; }