C-班长竞选

C-班长竞选

一、题目描述

大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,
意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 
勤劳的 TT 收集了M条意见,想要知道最高票数,
并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?

Input

本题有多组数据。第一行 T 表示数据组数。
每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),
接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。

Output

对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 
接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!

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

二、思路与算法

本题主要算法为Kosaraju算法。
Kosaraju算法中,首先dfs一次确定原图的逆后序序列,之后第二遍 dfs,在反图中按照逆后序序列进行遍历。
(反图即将原图中的有向边反向)
(每次由起点遍历到的点即构成一个 SCC)
Kosaraju算法主要作用就是寻找图中所有SCC,所以如果需要求SCC,可以优先考虑Korasaju算法。

本题中为了求最高票数和获得最高票数的人,所以是有向图,需要先求出 SCC 再进行缩点,即将互相可达与单向可达分开考虑。
• 缩点后,不难发现对于属于第 i 个 SCC 的点来说,答案分为两部分,令 SCC[i] 表示第 i 个 SCC 中点的个数。
(1)当前 SCC 中的点,ans += SCC[i] – 1(去除自己)
(2)其它 SCC 中的点 SUM ( SCC[ j] ),其中 j 可到达 i
最终结果一定在于出度为0的SCC中。(可用反证法证明)
因此我们计算出反图,对每个入度为 0 的点进行 dfs,计算其 能到达的点的 SUM(SCC[ j]),即可得到答案。

三、代码实现

#include<cstdio>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;

int n,c[5500],dfn[5500],vis[5500],dcnt=0,scnt=0;
//dcnt - dfs序计数,scnt - scc计数
//dfn[i] - dfs后序列中第i个点
//c[i] - i号点所在的scc编号
vector<int> G1[5500];
vector<int> G2[5500]; //G1 - 原图,G2 - 反图
vector<int> G3[5500];  //scc图 
int N=0,M=0,T=0;  //N个点,M条边,T组数据 
int in_deg[5500]={0};//记录各点入度 
int result=0; 
vector<int> S[5500];//存储每个scc中的元素

void dfs1(int x){
	vis[x]=1;
	for(int i=0;i<G1[x].size();i++){
		int y=G1[x][i]; 
		if(!vis[y]){dfs1(y);	}
	}
	dfn[++dcnt]=x;
} 

void dfs2(int x){
	c[x]=scnt;
	for(int i=0;i<G2[x].size();i++){
		int y=G2[x][i];
		if(!c[y]){dfs2(y);	}
	}
}

int dfs3(int x){//计算入度为0的点的sum 
	vis[x]=1;//打标记
	int weight=S[x].size();//所在scc的点数 
	for(int i=0;i<G3[x].size();i++){
		if(vis[G3[x][i]]==0){//没有访问过 
			weight=weight+dfs3(G3[x][i]);
		} 
	} 
	return weight;
}

void kosaraju(){
	//初始化
	dcnt=0;	scnt=0;
	memset(c,0,sizeof(c));
	memset(vis,0,sizeof(vis));
	//第一遍dfs
	for(int i=0;i<=N;i++){
		if(!vis[i]){dfs1(i);	}
	}
	//第二遍
	for(int i=N-1;i>=1;i--){
		if(!c[dfn[i]]){
			++scnt;
			dfs2(dfn[i]);
		}
	} 
}

int main(){
	scanf("%d",&T);
	for(int i=1;i<=T;i++){
		scanf("%d %d",&N,&M);
		for(int j=0;j<N;j++){
			G1[i].clear();	G2[i].clear();
		}
		for(int j=0;j<M;j++){
			int t1=0,t2=0;
			scanf("%d %d",&t1,&t2);
			G1[t1].push_back(t2);
			G2[t2].push_back(t1);
		}
		kosaraju(); 
		//构建scc图 
		for(int j=0;j<N;j++){
			S[c[j]].push_back(j);//加入相应scc中 
			for(int k=0;k<G1[j].size();k++){
				if(c[j]!=c[G1[j][k]]){//j通往的点若不在同一个scc中 
					G3[c[G1[j][k]]].push_back(c[j]);//这里一定注意!!别忘了! 
					in_deg[c[j]]++;
				}
			}
		}
		for(int j=1;j<=scnt;j++){//每个scc中去重 
			sort(G3[j].begin(),G3[j].end());
			G3[j].erase(unique(G3[j].begin(),G3[j].end()),G3[j].end());
		}
		//反图中入度为0的是最终结果 
		int sum[5500];
		memset(sum,0,sizeof(sum));
		for(int j=1;j<=scnt;j++){
			if(in_deg[j]==0){//入度为0 
				memset(vis,0,sizeof(vis));
				sum[j]=dfs3(j)-1;
			}
		}
		result=0;
		for(int j=1;j<=scnt;j++){
			if(result<sum[j]){
				result=sum[j];
			}
		}
		int zero[5500];
		int num=0;
		for(int j=1;j<=scnt;j++)
		{
			if(sum[j]==result) 
			{
				for(int k=0;k<S[j].size();k++)
				{
					zero[num]=S[j][k];
					num++;
				}
			}
		}
		sort(zero,zero+num);//升序排列 
		printf("Case %d: %d\n",i,result);
		int j=0;
		for(j=0;j<num-1;j++){
			printf("%d ",zero[j]);
		}
		printf("%d\n",zero[j]);
		
		
		for(int j=0;j<5500;j++){//为处理下一组数据准备 
			G1[j].clear();	G2[j].clear();	G3[j].clear();	S[j].clear();
			vis[j]=0;	c[j]=0;		dfn[j]=0;	in_deg[j]=0;
		}
	}
	
	return 0;
} 

四、经验与总结

  1. 因为有很多循环,所以一定要注意每个循环的起始和结尾!从0还是1开始,最后有没有等于号,同时要与存储和处理的编号相匹配。
  2. 注意!!容易忘记,构造scc图时,一定要把边的两个端点都处理了,否则会遗漏点!(例如有的点出度为0,如果不在被到达的时候加入强连通分支中,那么它就不会被处理)
  3. 各种数据为准备处理下一组数据而进行的初始化,一定要仔细!要在合适的位置清空或置常数!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
优秀班委选举系统 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace 优秀班委选举 { public partial class Form1 : Form { private ListBox listBox; public Form1() { InitializeComponent(); } public Form1(ListBox listBox) { InitializeComponent(); this.listBox = listBox; } private void button1_Click(object sender, EventArgs e) { if (listBox1.Items.Count > 0) { if (listBox1.SelectedItem == null) { MessageBox.Show("请先选择一个对象!"); } else { //获取左边listBox1中选中的内容 string cont = listBox1.SelectedItem.ToString(); //左边listBox1中删除选中的内容 listBox1.Items.Remove(cont); //把获得的内容加入到右边的listBox2中 listBox2.Items.Add(cont); } } } private void button2_Click(object sender, EventArgs e) { if (listBox2.Items.Count > 0) { if (listBox2.SelectedItem == null) { MessageBox.Show("请先选择一个对象!"); } else { //获取右边listBox2中选中的内容 string cont = listBox2.SelectedItem.ToString(); //右边listBox2中删除选中的内容 listBox2.Items.Remove(cont); //把获得的内容加入到左边的listBox1中 listBox1.Items.Add(cont); } } } //退出 private void button5_Click(object sender, EventArgs e) { Application.Exit(); } //显示结果 private void button4_Click(object sender, EventArgs e) { //传递listBox2到Form2窗口中 Form2 form2 = new Form2(listBox2); form2.Show(); } //修改名单 private void button3_Click(object sender, EventArgs e) { if (listBox2.SelectedItem == null) { MessageBox.Show("请先选择一个对象!"); } else { string cont = listBox2.SelectedItem.ToString(); int index = listBox2.SelectedIndex; Form3 form3 = new Form3(cont, index, listBox2); form3.Show(); this.Hide(); } } //加载窗口 private void Form1_Load(object sender, EventArgs e) { if (listBox != null) { foreach (string str in listBox.Items) { listBox2.Items.Add(str); } } } } }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值