hdu3861——tarjan缩点+树形dp

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=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

题目翻译:

幼儿园的孩子喜欢玩一种叫做"鹰和鸡"的游戏。但总有一个大问题:在这个游戏里,每个孩子都想扮演老鹰的角色。‎
‎于是老师想出了一个主意:投票。每个孩子都有一些漂亮的手帕,如果他/她认为某人适合霍克的角色,他/她给这个孩子一个手帕,这意味着这个得到手帕的孩子赢得了支持。请注意,支持可以传输。获得最多支持的孩子在投票中获胜,并能够扮演"鹰"的角色。(注意:如果 A 可以‎
‎赢得 B (A ! = B) 的支持,则 A 在任何情况下都只能从 B 赢得一个支持,从 B 传输到 A 的支持数量很多。A在任何情况下都不能赢得自己的支持。‎
‎如果两个或两个以上的孩子拥有相同数量的支持,我们会把他们都当作胜利者。‎
‎这里有一个示例:3个孩子A,B和C,A给B手帕,B给C手帕,所以C赢得2个支持,他选择成为老鹰。‎

‎输入‎

‎有几个测试用例。首先是整数 T(T <= 50),表示测试用例的数量。‎
‎每个测试用例从两个整数 n 开头,m 在一行中(2 <= n <= 5000,0 <m <= 30000)。n 表示有 n 个子级(编号从 0 到 n - 1)。以下每行包含两个整数 A 和 B(A != B),表示编号为 A 的子项向 B 提供手帕。‎

‎输出‎

‎对于每个测试用例,输出应首先包含一行"case x:",此处 x 表示案例编号从 1 开始。后面跟一个数字,这是获胜者得到‎‎的总支持。‎
‎然后沿着包含所有鹰队的号码的线走。数字必须按增加顺序列出,并用单个空格分隔。

 

这个题的做法很巧妙,如果我们正面直接去做的话,代码很不容易实现,因此,秉着正难则反的原则,我们要想想怎么去反着来。其实就是先按顺序去建图,同时存下来每个连通分量的权值,然后缩点后再逆着建图,很容易知道入度为0就是胜利者。

之后再对逆着建的图进行dfs,求出总支持个数=它所在的强连通分量中的点数-1+后代中的总支持个数。

#include <bits/stdc++.h>
using namespace std;
const int maxn=5e3+7;
int pre[maxn],low[maxn],sccno[maxn],num[maxn];
int in0[maxn];
int visited[maxn],f[maxn];
int n,m;
int dfs_clock,scc_cnt;
stack<int> s;
vector<int> G[maxn];
vector<int> G2[maxn];
void dfs(int u){
	low[u]=pre[u]=++dfs_clock;
	s.push(u);
	for(int i = 0;i<(int)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(1){
			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(low,0,sizeof(low));
	memset(sccno,0,sizeof(sccno));
	for(int i = 0;i<n;++i)
		if(!pre[i])
			dfs(i);
}
int dfs2(int u){
	int sum=0;
	visited[u]=1;
	for(int i = 0;i<(int)G2[u].size();++i){
		int v=G2[u][i];
		if(!visited[v]){
			sum+=dfs2(v)+num[v];
		}
	}
	return sum;
}
int main(int argc, char** argv) {
	int T;
	cin>>T;
	for(int ncase=1;ncase<=T;++ncase){
		printf("Case %d: ",ncase);
		scanf("%d%d",&n,&m);
		for(int i = 0;i<n;++i) G[i].clear();
		while(m--){
			int x,y;
			scanf("%d%d",&x,&y);
			G[x].push_back(y);
		}
		find_scc(n);
		for(int i = 1;i<=scc_cnt;++i){
			G2[i].clear();
			in0[i]=1;
		}
		for(int u = 0;u<n;++u){
			for(int  i=0;i<(int)G[u].size();++i){
				int v=G[u][i];
				if(sccno[u]!=sccno[v]){
					in0[sccno[u]]=0;
					G2[sccno[v]].push_back(sccno[u]);
				}
			}
		}
		memset(f,0,sizeof(f));
		int maxf=-1;
		for(int i = 1;i<=scc_cnt;++i){
			if(in0[i]){
				memset(visited,0,sizeof(visited));
				f[i]=num[i]-1+dfs2(i);
				maxf=max(maxf,f[i]);
			}
		}
		int first=1;
		printf("%d\n",maxf);
		for(int i = 0;i<n;++i){
			if(f[sccno[i]]==maxf){
				if(first) printf("%d",i),first=0;
				else printf(" %d",i); 
			}
		}
		printf("\n");
	}
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值