Hawk-and-Chicken HDU - 3639,Tarjan缩点,DAG反建图

Hawk-and-Chicken

  HDU - 3639 

Kids in kindergarten enjoy playing a game called Hawk-and-Chicken. But there always exists a big problem: every kid in this game want to play the role of Hawk. 
So the teacher came up with an idea: Vote. Every child have some nice handkerchiefs, and if he/she think someone is suitable for the role of Hawk, he/she gives a handkerchief to this kid, which means this kid who is given the handkerchief win the support. Note the support can be transmitted. Kids who get the most supports win in the vote and able to play the role of Hawk.(A note:if A can win 
support from B(A != B) A can win only one support from B in any case the number of the supports transmitted from B to A are many. And A can't win the support from himself in any case. 
If two or more kids own the same number of support from others, we treat all of them as winner. 
Here's a sample: 3 kids A, B and C, A gives a handkerchief to B, B gives a handkerchief to C, so C wins 2 supports and he is choosen to be the Hawk.
Input
There are several test cases. First is a integer T(T <= 50), means the number of test cases. 
Each test case start with two integer n, m in a line (2 <= n <= 5000, 0 <m <= 30000). n means there are n children(numbered from 0 to n - 1). Each of the following m lines contains two integers A and B(A != B) denoting that the child numbered A give a handkerchief to B.
Output
For each test case, the output should first contain one line with "Case x:", here x means the case number start from 1. Followed by one number which is the total supports the winner(s) get. 
Then follow a line contain all the Hawks' number. The numbers must be listed in increasing order and separated by single spaces.
Sample Input
2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2
Sample Output
Case 1: 2
0 1
Case 2: 2
0 1 2
题解:

这道题目,要求统计每个小孩子获得支持的数量,(属于同一个强连通分量的小孩子所获得的支持数相同)。

所以第一步,我们需要对原图用Tarjan进行缩点,形成DAG,每个缩点所拥有的支持数表示为该SCC中所含节点的总数。

如果一个小孩A支持了其他的孩子B,那么B所获得的支持数一定会大于A所获得的支持数,最后的答案一定在原DAG图出度为0的那些点上。

我们将DAG反向建图,然后对所有入度为0的点进行统计,统计他所能达到的节点数,也就是他所获得的支持数。(这里要注意,每次统计一个新的节点的时候,要把数组初始化,并且千万不要重复统计,我在这里因为重复统计WA了好久好久。。。)

然后比较所有的统计值,选最大的那个,并把答案-1输出(自己不能支持自己,把自己的那一票去除)


这道题我一开始缩完点后想用拓扑排序来dp的,后来失败了,原因是重复统计,画个图。


这样的话,最左边的点贡献了2次,最右边节点得到了10,而其实应该贡献1次,也就是最右边节点应该是8才对。

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;
const int MAXN = 5005;
int head[MAXN];
int DFN[MAXN];
int LOW[MAXN];
int visit[MAXN];
int stack[MAXN];
int visit2[MAXN];
vector<int> scc[MAXN];
set<int> G[MAXN];
int sup[MAXN];
int belong[MAXN];
int sp2;
int tot;
int index;
int cnt;
int sccnum;
int stk[MAXN];
int indegree[MAXN];
struct Es{ 
    int v; 
    int next; 
    int cost; 
}Es[60007];  
void init(){ 
    sp2 = sccnum = tot = index = cnt = 0; 
    memset(head,-1,sizeof(head)); 
    memset(DFN,0,sizeof(DFN)); 
    memset(LOW,0,sizeof(LOW)); 
    memset(indegree,0,sizeof(indegree)); 
    memset(visit,0,sizeof(visit)); 
    memset(visit2,0,sizeof(visit2)); 
    memset(belong,0,sizeof(belong)); 
	memset(sup,0,sizeof(sup)); 
    for(int i = 0;i < MAXN;i++) scc[i].clear(),G[i].clear();
}
inline void add_edge(int i,int j,int cost){   
    Es[cnt].v = j; 
    Es[cnt].cost = cost; 
    Es[cnt].next = head[i]; 
    head[i] = cnt++; 
}   
void tarjan(int x)//代表第几个点在处理。递归的是点。
{
     DFN[x]=LOW[x]=++tot;// 新进点的初始化。
     stack[++index]=x;//进站
     visit[x]=1;//表示在栈里
     for(int i=head[x];i!=-1;i=Es[i].next)
     {
         if(!DFN[Es[i].v]) {//如果没访问过
             tarjan(Es[i].v);//往下进行延伸,开始递归
             LOW[x]=min(LOW[x],LOW[Es[i].v]);//递归出来,比较谁是谁的儿子/父亲,就是树的对应关系,涉及到强连通分量子树最小根的事情。
         }
         else if(visit[Es[i].v]){ 
             LOW[x]=min(LOW[x],DFN[Es[i].v]);
         }
     }
     if(LOW[x]==DFN[x]) 
     {
     	 ++sccnum;
         do{
             int item = stack[index];
             belong[item] = sccnum;
             scc[sccnum].push_back(item);
             visit[item]=0;
             index--;
         }while(x!=stack[index+1]);
     }
     return ;
}

void dfs(int x){ 
	sup[x] = scc[x].size(); 
	visit2[x] = 1;
	set<int>::iterator it = G[x].begin();
	for(;it != G[x].end();++it){
		int v = *it;
		if(!visit2[v]) {
			dfs(v);
			sup[x] += sup[v];
		}
	}
}
int n,m;
int main(){
	int T,cas = 0;
	cin>>T;
	while(T--){
		init();
		scanf("%d%d",&n,&m);
		for(int i = 0;i < m;i++){
			int a,b;
			scanf("%d%d",&a,&b);
			a++,b++;
			add_edge(a,b,1); 
		} 
		for(int i = 1;i <= n;i++)
			if(!DFN[i])
				tarjan(i);
		//sp2 = sccnum;
		
		for(int i = 1;i <= n;i++){
			for(int e = head[i];e != -1;e = Es[e].next){
				int v = Es[e].v;
				if(belong[i] != belong[v]){
					G[belong[v]].insert(belong[i]);
					indegree[belong[i]]++;
				}
			}
		} 
		
		for(int i = 1;i <= sccnum;i++){
			if(!indegree[i]){
				memset(visit2,0,sizeof(visit2));
				dfs(i);
			} 
		}
		int ans = -1;
		for(int i = 1;i <= sccnum;i++){
			if(sup[i] > ans){
				ans = sup[i];
			}
		}
		printf("Case %d: %d\n",++cas,ans - 1);
		int f = 0;
		for(int i = 1;i <= n;i++){
			if(sup[belong[i]] == ans){
				if(f)
					putchar(' ');
				else
					f = 1;
				printf("%d",i-1);
			} 
		} 
		puts("");
		
	} 
	return 0;
} 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值