Hawk-and-Chicken详解

题目描述:

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.

有n个小朋友在一个班级中,现在要选择班长。收集了小朋友们的意见,一条意见表示为A认为B合适。这个是具备传递性的,A认为B合适,B认为C合适。那么A也会认为C合适。
现在需要提供一份候选人名单,这里面的人,是被最多的人,认为合适的。

输入:

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.
第一个整数,表示测试数据的组数。
每组数据,第一行两个整数n和m
接下来m行,每行两个整数a,b,表示a认为b合适,小朋友的编号为0~n-1

输出:

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.
对于每组数据,先输出Case编号。接下来一个整数表示最多的票数。接下来是满足条件的小朋友编号,从小到大输出。

样例输入:

2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2

样例输出:

Case 1: 2
0 1
Case 2: 2
0 1 2

写这道题目的时候发现好多人使用tarjan算法
后来想到似乎在书上有一道类似的模板题目,但因为不是很明白结构,所以只能原样套用而不会更改
想了很久总算是想明白了一点,现在先记一下

强连通分量可以通过两次简单的DFS实现,第一次DFS时,选取任意的顶点作为起点,遍历所有尚未访问过的顶点,并在回溯前给顶点编号,对剩余未访问过的顶点,不断重复上述过程
完成编号后,越接近图的尾部(搜索树的叶子),顶点的编号越小,第二次DFS时,先将所有的边反向,然后以标号最大的顶点为起点DFS,之后,只要还有尚未访问过的顶点,就从中选取标号最大的顶点不断重复上述过程
(在第二次DFS时,相当于tarjan算法中栈内不断弹出的过程,每一个被访问的顶点,都是一个强联通分量的根节点)
举个例子:
在这里插入图片描述
在这个图里我们以1位根节点(当然,你也可以换,不要忘了顺序就好,为了方便,通常以编号最小的那个开始),在第一次dfs中:
1---------->2
2---------->3
位置3:
发现没有没有可以继续往下走了,3没有指向的地方,于是回到2
位置2:
2也没有没有被访问过的点了,回到1
位置1:
1也没有了,第一次的dfs到此结束
在访问时,我们标记一下点,从内到外依次是:
3 ------- 2 ---------1
第二次dfs 我们反向建图,并按照标记的点反向寻找
在这里插入图片描述
原先标记是3–2—1
反向寻找,也就是1–2----3
位置1:
没有指向的点,下一个
位置2:
指向1,但是1已经被访问过了,下一个
位置3:
指向的数字都被访问完了,第二次dfs结束
然后你就会有一种疑惑,所以这两次dfs到底干嘛的???
当然是,缩点啊
刚刚的例子只是热身,方便理解,换一个样例看看

在这里插入图片描述
第二个样例:
第一次dfs
顺序是0-------->>>>2-------------->>>>1
按照访问的顺序从内而外是
1--------2--------0
在这里插入图片描述
第二次dfs 反向建图
按照上述的顺序反向寻找
第一个数字:0
0------->>1----------->>2
发现用第一个数字就可以将整个成环状的图都过了一遍
就可以将他们缩成一个点
在这里插入图片描述
复杂一点的图就像这样
在这里插入图片描述
这样就建图完成啦,随后就可以根据题目要求书写啦~~
本题代码如下(这道题因为时间允许使用的是vector连接,可以换成邻接表)

code:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<vector>
#include<stack>
using namespace std;
int v,m;
int cnt=0;
const int maxn=5050;
const int maxm=30010;
vector<int> g[maxn];//正向建图的连接
vector<int> rg[maxn];//反向建图的连接
vector<int> vs;//标记第一次dfs时从内往外的数字
vector<int> jiang[maxn];//缩点之后的反向连接
bool used[maxn];
int in[maxn];//判断是否这个点有指向的点
int much[maxn];//缩点之后该点代表的数字个数
int sum[maxn];//那块联通分量的点的个数
int cmp[maxn];//缩点之后该点的表示
int visit[maxn];//缩点之后检验是否被访问
int a[maxm];
int b[maxm];
void add_edge(int from,int to)
{
	g[from].push_back(to);
	rg[to].push_back(from);
}//正向与反向建图
void dfs(int v)
{
	used[v]=true;
	for(int i=0;i<g[v].size();i++)
	{
		if(!used[g[v][i]])dfs(g[v][i]);
	}
	vs.push_back(v);
}//第一遍dfs
int findd(int what)
{
	visit[what]=1;
	int temp=much[what];
	for(int i=0;i<jiang[what].size();i++)
	{
		if(visit[jiang[what][i]]==0)temp+=findd(jiang[what][i]);
	}
	return temp;
}//缩点之后反向寻找到底有多少人觉得他适合
void rdfs(int v,int k)
{
	used[v]=true;
	cmp[v]=k;
	much[k]++;
	for(int i=0;i<rg[v].size();i++)
	{
		if(!used[rg[v][i]]){
			rdfs(rg[v][i],k);
		}
	}
}//反向寻找
void scc(int ca)
{
	memset(used,0,sizeof(used));
	vs.clear();
	for(int i=0;i<v;i++)
	{
		if(!used[i])dfs(i);
	}
	memset(used,0,sizeof(used));
	memset(much,0,sizeof(much));
	int k=1;
	for(int i=vs.size()-1;i>=0;i--)
	{
		if(!used[vs[i]])rdfs(vs[i],k++);
	}//到这一步缩点完成,cmp[i]代表缩点之后他的新名字
	memset(in,0,sizeof(in));
	memset(sum,0,sizeof(sum));
	for(int i=0;i<m;i++)
	{
		jiang[cmp[b[i]]].push_back(cmp[a[i]]);
		if(cmp[b[i]]!=cmp[a[i]])in[cmp[a[i]]]++;
	}//判断是否出度为0
	int maxx=-1;
	for(int i=1;i<k;i++)
	{
		if(in[i]==0)
		{
			memset(visit,0,sizeof(visit));
			sum[i]+=findd(i);
			maxx=max(maxx,sum[i]);
		}
	}//寻找到底有多少人觉得他合适(包括他自己,后来减掉就好了)
	printf("Case %d: %d\n",ca,maxx-1);
	int flag=0;
	for(int i=0;i<v;i++)
	{
		if(sum[cmp[i]]==maxx)
		{
			if(flag==0){
				printf("%d",i);
				flag=1;
			}
			else
			printf(" %d",i);
		}
	}
	printf("\n");
}
int main()
{
	int ttt;
	scanf("%d",&ttt);
	for(int i=1;i<=ttt;i++)
	{
		scanf("%d%d",&v,&m);
		for(int i=0;i<=v;i++)
		{
			g[i].clear();
			rg[i].clear();
			jiang[i].clear();
		}
		for(int i=0;i<m;i++)
		{
			scanf("%d%d",&a[i],&b[i]);
			add_edge(a[i],b[i]);
		}
		scc(i);
	}
	return 0;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值