王道机试 图论

图的数据结构与基本操作

用vector模拟,基本操作如下:

#include<iostream>
#include<vector>
using namespace std;
const int N=1;
struct node {
	int nextnode;//下一个节点编号
	int cost;
};
vector<node>Adj[N];//一个Adjvex[i]是一个不定长数组,表示一个node

void graph_clear() {
	for(int i=0; i<N; i++)
		Adj[i].clear();
}
void init() {//先建图
	node t;
	for(int i=0; i<N; i++) {
		t.nextnode=3;
		t.cost=48;
		Adj[i].push_back(t);
	}
}
void traverse() {//遍历
	for(int i=0; i<N; i++) {
		for(int j=0; j<Adj[i].size(); j++) { //一个Adj[i]是一个顶点
			printf("%d ", Adj[i][j].nextnode);
			printf("%d ", Adj[i][j].cost);
		}
	}
}
int main() {
	graph_clear();
	init();
	traverse();
	return 0;
}

case1:Battle Over Cities,懂题意最重要,然后判断联通块,DFS或并查集(注意都需要用vec e[N]存图,然后再father)都可以

// #include<bits/stdc++.h>
// #include<vector>
// using namespace std;
// const int N=1005;
// vector<int>e[N];
// int tree[N];//并查集
// int findRoot(int x) {
// 	if(tree[x]==-1) return x;//他爸是-1
// 	else {
// 		int root=findRoot(tree[x]);
// 		tree[x]=root;
// 		return root;
// 	}
// }
// int n,m,k;//城市,路,query
// /*
// 注意:还是要先存一个edge,然后在相应的query时做联通操作
// */
// int main() {
// 	cin>>n>>m>>k;
// 	while(m--) {
// 		int a,b;
// 		cin>>a>>b;
// 		e[a].push_back(b);
// 		e[b].push_back(a);
// 	}
// 	while(k--) {
// 		int q,ans=-1;//query
// 		cin>>q;
// 		memset(tree,-1,sizeof(tree));
// 		for(int i=1; i<=n; i++) {
// 			for(int j=0; j<e[i].size(); j++) {
// 				if(i==q || e[i][j]==q) continue;
// 				int a=findRoot(i);
// 				int b=findRoot(e[i][j]);
// 				if(a!=b) tree[a]=b;
// 			}
// 		}
// 		for(int i=1; i<=n; i++) {
// 			if(i==q) continue;
// 			if(tree[i]==-1) ans++;
// 		}
// 		printf("%d\n",ans);
// 	}

// 	return 0;
// }

//法2:DFS
#include<bits/stdc++.h>
#include<vector>
using namespace std;
const int N=1005;
vector<int>e[N];
int n,m,k;//城市,路,query
int q,ans=-1;//query
int vis[N]= {0};
/*
注意:还是要先存一个edge,然后在相应的query时做联通操作
*/
void DFS(int x) {
	for(int j=0; j<e[x].size(); j++) {
		int nd=e[x][j];
		if(nd!=q && !vis[nd]) {
			vis[nd]=1;
			DFS(e[x][j]);
		}
	}
}
int main() {
	cin>>n>>m>>k;
	while(m--) {
		int a,b;
		cin>>a>>b;
		e[a].push_back(b);
		e[b].push_back(a);
	}
	while(k--) {
		cin>>q;
		ans=-1;
		memset(vis,0,sizeof(vis));
		for(int i=1; i<=n; i++)
			if(i!=q && !vis[i]) {
				ans++;
				DFS(i);
			}
		printf("%d\n",ans);
	}
	return 0;
}

case2:Forwards on Weibo,虽然30分,但是感觉超简单,BFS一把过

#include<bits/stdc++.h>
#include<vector>
#include<queue>
using namespace std;
const int N=1004;
vector<int>e[N];
int layer[N]= {0};
int vis[N]= {0};
int n,L,k,ans;//人数,层数限制

void BFS(int x) {
	queue<int>q;
	q.push(x);
	while(!q.empty()) {
		int t=q.front();
//		cout<<t<<' ';
		if(layer[t]>=L) break;
		q.pop();
		for(int i=0; i<e[t].size(); i++) {
			int nd=e[t][i];
			if(vis[nd]) continue;
			vis[nd]=1;
			layer[nd]=layer[t]+1;
			q.push(nd);
			ans++;
		}
	}
}

int main() {
	cin>>n>>L;
	for(int i=1,a,t; i<=n; i++) {
		cin>>t;
		while(t--) {
			cin>>a;
			e[a].push_back(i);
		}
	}
	cin>>k;
	while(k--) {
		int x;
		cin>>x;
		ans=0;
		memset(layer,0,sizeof(layer));
		memset(vis,0,sizeof(vis));
		vis[x]=1;
		BFS(x);
		cout<<ans<<endl;
	}
	return 0;
}

并查集

  • 用 tree数组来表示集合,每个元素的值表示其父节点,tree[x]==-1表示x为根
  • 路径压缩:每次将元素直接指向该集合的root,而不要通过传递来找到,这样可以显著提高查询效率
  • “合并”x和y,也就是将他们放到同一棵树上,编程可以tree[x]=y这样

case1:畅通工程,将问题抽象成并查集----联通的城市可以表示到一个集合里(一棵树),最后遍历所有城市,得到tree[x]==-1的个数n,即有n个集合->n个不连通的城市,so 需要修建的道路数ans=n-1

#include<iostream>
#include<cstring>
using namespace std;
const int N=1005;
int tree[N];//用树表示集合,每个元素的值是它的父节点编号

int find_root(int x) {
	if(tree[x]==-1) return x;
	else {
		int root=find_root(tree[x]);//递归找根
		tree[x]=root;//路径压缩 
		return root;
	}
}
int find_root2(int x) {
	int root, t=x;
	while(tree[x]!=-1)
		x=tree[x];
	root=x;
	
	x=t;//回去,以路径压缩 
	while(tree[x]!=-1){
		t=tree[x];
		tree[x]=root;
		x=t;
	}
	return root;
}

int main() {
	int n,m;
	while(scanf("%d", &n)!=EOF, n!=0) {
		memset(tree,-1,sizeof(tree));
		scanf("%d", &m);
		while(m--) {
			int a,b;
			scanf("%d%d", &a,&b);
			a=find_root2(a);
			b=find_root2(b);
			if(a!=b) tree[a]=b;
		}
		int ans=0;
		for(int i=1; i<=n; i++) { //遍历每个城镇,如果是-1,说明是一个区域。注意编号是从1开始的 
//			cout<<tree[i]<<' ';
			if(tree[i]==-1) ans++;
		}
		printf("%d\n", ans-1);
	}
	return 0;
}

case2:more is better,实话说我没看懂题目。。看了解析才明白是找一个有最多元素集合的元素数

  • 用sum来存储每个集合的元素数,用fill函数init为1,然后每次合并时,sum[b]+=sum[a]即可
  • 最后O(n)找max,别稀里糊涂sort还搞TLE了
#include<iostream>
#include<map>//不能直接用sort排序
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e7+5;
int tree[N];//用树表示集合,每个元素的值是它的父节点编号
int sum[N];//存一个集合内的元素数 

int find_root(int x) {
	if(tree[x]==-1) return x;
	else {
		int root=find_root(tree[x]);
		tree[x]=root;
		return root;
	}
}
int main() {
	int n;
	while(scanf("%d", &n)!=EOF) {
		memset(tree,-1,sizeof(tree));
		fill(sum,sum+N,1);//初始化为 1个人4
		while(n--) {
			int a,b;
			scanf("%d%d", &a,&b);
			a=find_root(a);
			b=find_root(b);
			if(a!=b) {
				tree[a]=b;
				sum[b]+=sum[a];//b是根,故a的加到 b上 
			}
		}
//		sort(sum,sum+N);这个是nlogn。。明明就是找一个max,O(n)就可以啊 
//		printf("%d\n", sum[N-1]);
		int maxx=0;
		for(int i=0;i<N;i++){
			if(tree[i]==-1 && sum[i]>maxx)
				maxx=sum[i];				
		}
		printf("%d\n", maxx);
	}
	return 0;
}

case3:判断连通图,开始还以为用图,后来发现完全又是并查集,检查最后tree[i]==-1的数量是否是1即可

#include<iostream>
#include<cstring>
using namespace std;
const int N=1005;
int tree[N];//用树表示集合,每个元素的值是它的父节点编号

int find_root(int x) {
	if(tree[x]==-1) return x;
	else {
		int root=find_root(tree[x]);//递归找根
		tree[x]=root;//路径压缩
		return root;
	}
}

int main() { //16:06->16:25
	int n,m;//顶点数,边数
	while(scanf("%d%d", &n,&m)!=EOF, n!=0) {
		memset(tree,-1,sizeof(tree));
		while(m--) {
			int a,b;
			scanf("%d%d", &a,&b);
			a=find_root(a);
			b=find_root(b);
			if(a!=b) tree[a]=b;
		}
		int ok=1;
		for(int i=1; i<=n&&ok>=0; i++)//“与”怎么写成逗号了呢 
			if(tree[i]==-1) {
				ok--;//小于0说明有两个root,说明不连通 
			}
		printf("%s\n",ok>=0?"YES":"NO");
	}
	return 0;
}

case4:Head of a Gang

是gang的条件:一个cluster的总time要>k且人数>2。选head原则:一个cluster里time最大的

最小生成树(MST)

case1:还是畅通工程,问要使各个村庄联通的最小修路长度

  • 利用struct存edge(本质为了存 两node间的cost,真正的edge是通过tree来存的)
  • kruskal算法,首先对edge的cost排序,遍历每一个"cost"之间的node是否在同一个集合(是否已经连通),如果不是就合并,然后加上两node之间的cost
  • 最后要判断全图是否连通了
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1005;
int tree[N];//记录node
struct Edge {
	int a,b;
	int cost;
} e[N]; //只是用于存储两node间的cost,而不一定真有这条edge!真正的edge本质在tree记录
int cmp(Edge a,Edge b) {
	return a.cost<b.cost;
}
int n,m;//道路数,村庄数
int find_root(int x) {
	if(tree[x]==-1) return x;
	else {
		int root=find_root(tree[x]);
		tree[x]=root;
		return root;
	}
}
bool is_connected() { //判断图是否连通
	for(int i=1,ok=1; i<=m; i++) {
		if(tree[i]==-1) ok--;
		if(ok<0) return false;
	}
	return true;
}
int main() {
	while(scanf("%d%d", &n,&m)!=EOF, n!=0) {
		memset(tree,-1,sizeof(tree));
		for(int i=1; i<=n; i++)
			scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].cost);
		sort(e+1,e+1+n,cmp);
		int ans=0;
		for(int i=1; i<=n; i++) {
			int a=find_root(e[i].a);
			int b=find_root(e[i].b);
			if(a!=b) { //判断一个edge两段的node是否已经联通
				tree[a]=b;
				ans+=e[i].cost;
			}
		}
		if(is_connected()) printf("%d\n", ans);//刚开始这个函数忘记打括号了。。没有运行
		else printf("?\n");
	}
	return 0;
}

case2:Freckles,给出了点的位置坐标,问连通的最短路径

  • 需要转换成每个edge(n*(n-1)/2个)然后MST
  • 在struct里面定义函数,成员函数?
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=105;
int tree[N];//记录node
struct Edge {
	int a,b;
	double cost;
} e[N*N/2];
bool cmp(Edge x,Edge y) {
	if(x.cost<y.cost && fabs(x.cost-y.cost)>1e-6)
		return true;
	else return false;
//	return x.cost<y.cost;
}
struct Node {
	double x,y;
	double get_dis(Node A) {
		return sqrt((x-A.x)*(x-A.x)+(y-A.y)*(y-A.y));
	}
} node[N];
int find_root(int x) {
	if(tree[x]==-1) return x;
	else {
		int root=find_root(tree[x]);
		tree[x]=root;
		return root;
	}
}
int main() { //11:40->12:50
	int n;
	while(scanf("%d", &n)!=EOF, n!=0) {
		memset(tree,-1,sizeof(tree));
		for(int i=1; i<=n; i++)
			scanf("%lf%lf", &node[i].x,&node[i].y);
		int e_n=0;//边数,最后是n*(n-1)/2
		for(int i=1; i<=n; i++) {
			for(int j=i+1; j<=n; j++) {
				e[e_n].a=i;
				e[e_n].b=j;
				e[e_n++].cost=node[i].get_dis(node[j]);
			}
		}
		double ans=0;
		sort(e,e+e_n,cmp);
		for(int i=0; i<e_n; i++) { //遍历每个edge,这里e从0开始,因为0/1无所谓
			int a=find_root(e[i].a);
			int b=find_root(e[i].b);
			if(a!=b) {
				tree[a]=b;
				ans+=e[i].cost;
//				printf("%.2lf\n", ans);
			}
		}
		printf("%.2lf\n", ans);
	}
	return 0;
}

case3:Jungle Roads不难,主要是复习kruskal算法了,值得注意的是

  • scanf("%c", &ch); 可以读入换行和空格,若想用scanf,就要在%c前加上前面的\n或者空格!否则会读错的
  • 名字是字母,在建并查集的时候,因为题目简单,直接对名字都-'A'就可以建立相应的编号了
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=30;
int tree[N];
struct Edge {
	char a,b;//名字就是一个字母,序号用 -'A'表示
	int cost;
} e[N*N];
int cmp(Edge x, Edge y) {
	return x.cost<y.cost;
}
int find_root(int x) {
	if(tree[x]==-1) return x;
	else {
		int root=find_root(tree[x]);
		tree[x]=root;
		return root;
	}
}
int main() {
	int num;//城市数
	while(scanf("%d", &num)!=EOF, num!=0) {
		memset(tree,-1,sizeof(tree));
		int e_n=0, n;//边数,一个node连接的node数
		char c1, c2;
		while(--num) {
			scanf("\n%c%d", &c1,&n);
//			cin>>c1>>n;//!错误原因是,scanf是可以吃空格的。。。
			int co;//cost
			while(n--) {
				e[e_n].a=c1;
				scanf(" %c%d", &c2,&co);
//				cin>>c2>>co;
				e[e_n].b=c2;
				e[e_n++].cost=co;
			}
		}
		sort(e,e+e_n,cmp);
		int ans=0;
		for(int i=0; i<e_n; i++) { //遍历每个edge
			int a=find_root(e[i].a-'A');
			int b=find_root(e[i].b-'A');
			if(a!=b) {
				tree[a]=b;
				ans+=e[i].cost;
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

case4:继续畅通工程,这题与case1不同之处在于,给了一些已经修好的路,问现在要全连通最少需要的cost。

  • 有个非常巧妙的做法就是在cmp函数,先按是否修好排序,再按cost升序排序!最后还是一样用MST做就完全一样了
  • 看到另一做法,错的!他还是完全按照cost排序,只是是在input的时候 对已经修好的路添加到并查集里面,但是sort绝对不能这么写,因为有的有的虽然cost大,但是已经连通了的话,宁可有更小cost的edge也不会再修建了
  • 在存储好所有edge后的合并操作,最后的结果一定是在给定数据内都连通。因为这里给了每个点之间的关系,共n*(n-1)/2个,全部连通后,在if(a!=b)那里始终不能通过,也就是说始终a==b;而有时候可能某两点之间压根没路,这样当然是无法连通了
        
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=105;
int tree[N];
struct Edge {
	int a,b;//名字就是一个字母,序号用 -'A'表示
	int cost;
	int ok;//是否已经建好
} e[N*N];
int cmp(Edge x, Edge y) {
	if(x.ok!=y.ok) return x.ok>y.ok;//修建好的优先!这个思路非常巧妙
	else return x.cost<y.cost;
}
int find_root(int x) {
	if(tree[x]==-1) return x;
	else {
		int root=find_root(tree[x]);
		tree[x]=root;
		return root;
	}
}
int main() {
	int n;//城市数
	while(scanf("%d", &n)!=EOF, n!=0) {
		memset(tree,-1,sizeof(tree));
		int m=n*(n-1)/2;
		for(int i=0; i<m; i++) {
			scanf("%d%d%d%d", &e[i].a,&e[i].b,&e[i].cost,&e[i].ok);
		}
		sort(e,e+m,cmp);
		int ans=0;
		for(int i=0; i<m; i++) { //遍历每个edge
			int a=find_root(e[i].a);
			int b=find_root(e[i].b);
			if(a!=b) {//这就是合并操作,最后的结果一定是在给定数据内都连通。
				//因为这里给了每个点之间的关系,而有时候可能某两点之间压根没路,这样当然是无法连通了
				tree[a]=b;
				if(e[i].ok==0)
					ans+=e[i].cost;
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

最短路径

case1:最短路

法1:Floyd算法

  • 先遍历每一个i,j之间的中介点,遇到i,j与k可达,且i->j的距离比通过k到要大时,更新
  • O(n^3)复杂度,一般只撑到200个node。一般用邻接矩阵存储(名字可以序号化,作为数组下标)
  • 注意无向图 input时,a->b, b->a都要赋值

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=205;
const int INF=0x3fffffff;
int mapp[N][N];//x->y的cost
int n,m;//路口数,道路数
void Floyd() {
	for(int k=1; k<=n; k++)
		for(int i=1; i<=n; i++)
			for(int j=1; j<=n; j++) {
				if(mapp[i][k]!=INF && mapp[k][j]!=INF &&
				        mapp[i][j]>mapp[i][k]+mapp[k][j])
					mapp[i][j]=mapp[i][k]+mapp[k][j];//否则都保持原样
			}
}
int main() {
	while(scanf("%d%d", &n,&m)!=EOF) {
		if(!n&&!m) break;
		fill(mapp[0],mapp[0]+N*N,INF);//除了 到自己,都init为INF
		for(int i=1; i<=n; i++) mapp[i][i]=0;
		while(m--) {
			int a,b,c;
			scanf("%d%d%d", &a,&b,&c);
			mapp[a][b]=mapp[b][a]=c;
		}
		Floyd();
		printf("%d\n", mapp[1][n]);
	}
	return 0;
}

法2:Dijkstra算法(单元最短路,一个node到其他all nodes的最短路)。(在牛课上,F法31ms,D法15ms)实现思路如下:

  • 邻接链表法存储。用vector结构体数组来存图,即一个node的邻node与cost
  • dis数组存开始节点s到各node的路径(算法结束后都是最短路),vis数组标记node是否已经扩展过【很像BFS】
  • 大循环n-1次,即需要遍历到n-1个节点(每次只添加一个node,so需要n-1次循环)
    • 大循环内,每扩展一个node p,需要扩展p的all 邻接节点t,若t不可达,或者通过p到t可以更短,则更新dis
    • 同时选择dis里面最小cost的node作为下一次扩展的node【整个算法很像BFS】
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N=205;
const int INF=0x3fffffff;
struct Node {
	int next;
	int cost;
};
vector<Node>e[N];//邻接链表存图
int n,m;//路口数,道路数
int dis[N];//存:从s到i的最短路
int mark[N];//标记是否已经得到:到i的最短路长度(即 是否已经在S集里)

void Dijkstra() {
	fill(dis,dis+N,INF);
	memset(mark,0,sizeof(mark));
	dis[1]=0;//到自己
	mark[1]=1;
	
	int p=1;//指新加的node
	for(int i=1; i<n; i++) { //n-1次,确定n-1个点的最短路 
		for(int j=0; j<e[p].size(); j++) { //遍历新加的点的all邻边
			int t=e[p][j].next;
			if(mark[t]) continue;
			int c=e[p][j].cost;
			if(dis[t]==INF || dis[t]>dis[p]+c)//t节点尚不可达,or由p到t更短
				dis[t]=dis[p]+c;
		}
		int mi=INF;
		for(int j=1; j<=n; j++) { //选下一个最短路的node
			if(mark[j] || dis[j]==INF) continue;
			if(dis[j]<mi) {
				mi=dis[j];
				p=j;
			}
		}
		mark[p]=1;
	}
}
int main() {
	while(scanf("%d%d", &n,&m)!=EOF) {
		if(!n&&!m) break;
		for(int i=1; i<=n; i++) e[i].clear();
		while(m--) {
			int a,b,c;
			scanf("%d%d%d", &a,&b,&c);
			Node t;
			t.next=b;
			t.cost=c;
			e[a].push_back(t);
			t.next=a;//无向图,两个方向都要添加
			e[b].push_back(t);
		}
		Dijkstra();
		printf("%d\n", dis[n]);
	}
	return 0;
}

case2:路径+花费的“优先最短路”,与之前不同的地方在于,路径一样的还要选花费少的,所以只要把“更新”和“选择”步骤里判断的条件加上路径相同时,cost少的优先就行啦!还有一点思考的就是,在“选择”里面,本质就是像BFS里面选frontier:已经扩展的node不选,完全没探索的节点不选。不过王道给出了更好写法:

  • if(dis[t]==INF || dis[t]>dis[p]+c || dis[t]==dis[p]+d && cost[t]>cost[p]+c) {dis[t]=dis[p]+d;cost[t]=cost[p]+c;} 合并一起写也很清晰
  • 另外,“选择下一个扩展结点”的时候,不需要考虑cost!?
  • 若最后vis[end_node]==0,说明不存在通路
#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
const int N=105;
vector<int>e[N];//邻接链表
queue<int>q;//保存入度为0的node
int inDegree[N];//统计每个node的入度

int main() {
	int n,m;//人数,关系数
	while(scanf("%d%d", &n,&m)!=EOF, n!=0) {
		for(int i=0;i<N;i++) e[i].clear();
		memset(inDegree,0,sizeof(inDegree));
		while(m--){
			int a,b;
			scanf("%d%d", &a,&b);
			inDegree[b]++;//入度++ 
			e[a].push_back(b);
		}
		while(!q.empty()) q.pop();//queue没有clear 
		for(int i=0;i<n;i++){
			if(inDegree[i]==0) q.push(i);//入度为0的入队 
		}
		
		int cnt=0;
		while(!q.empty()){
			int p=q.front();//入度为0的node p
			cnt++;
			q.pop();
			for(int i=0;i<e[p].size();i++){//遍历p指向的node 
				if(--inDegree[e[p][i]]==0) q.push(e[p][i]);
			}			
		}
		if(cnt==n) printf("YES\n");//all node能被确定拓扑排序,说明无环 
		else printf("NO\n");
	}
	return 0;
}

case3:

DAG的拓扑排序

若a->b则,a必须在b前面,求:满足这样要求的结点序列的过程,称拓扑排序,一个应用如下:

case1:Legal or Not,就是给出了谁是谁master的关系,有传递性,但不能A是B的同时B是A的,本质就是抽象为图的话,不能有环,于是可以用拓扑排序做,如果最终排序的结点数==总人数,说明排序完毕,否则说明有剩余 也就是说明有环。而拓扑排序的编程实现如下:

  • 利用一个queue保存all入度为0的node,vector e模拟邻接链表存图,inDegree存node的入度数
  • input时存图,之后把入度为0的都入队q,然后从q选一个p出队(从图中删去),并将p指向的node的入度都-1,若其入度也=0了,就入q,直到q为空。若all node都被删去(都出队了一次)说明拓扑排序完成,若没有,则说明有入度!=0的node,也就是环(因为有环的话,环内node的入度都不会到0),拓扑排序失败
  • 本题一次出队就cnt++,最后cnt==n就说明legal,也就是无环 成功,否则失败
#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
const int N=105;
vector<int>e[N];//邻接链表
queue<int>q;//保存入度为0的node
int inDegree[N];//统计每个node的入度

int main() {
	int n,m;//人数,关系数
	while(scanf("%d%d", &n,&m)!=EOF, n!=0) {
		for(int i=0;i<N;i++) e[i].clear();
		memset(inDegree,0,sizeof(inDegree));
		while(m--){
			int a,b;
			scanf("%d%d", &a,&b);
			inDegree[b]++;//入度++ 
			e[a].push_back(b);
		}
		while(!q.empty()) q.pop();//queue没有clear 
		for(int i=0;i<n;i++){
			if(inDegree[i]==0) q.push(i);//入度为0的入队 
		}
		
		int cnt=0;
		while(!q.empty()){
			int p=q.front();//入度为0的node p
			cnt++;
			q.pop();
			for(int i=0;i<e[p].size();i++){//遍历p指向的node 
				if(--inDegree[e[p][i]]==0) q.push(e[p][i]);
			}			
		}
		if(cnt==n) printf("YES\n");//all node能被确定拓扑排序,说明无环 
		else printf("NO\n");
	}
	return 0;
}

case2:确定比赛名次

  • 改用了优先队列,queue中用q.front()取元素,而priority_queue用q.top()取。学习优先队列的用法
  • 主要init问题!这里需要初始化的有4个:保存入度的q队列、存入度数的inDegree数组、邻接链表e数据、ans数组
#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
const int N=505;
vector<int>e[N];//邻接链表
priority_queue<int,vector<int>,greater<int>>q;//保存入度为0的node
int inDegree[N];//统计每个node的入度
int n,m;//队伍数,关系数
vector<int>ans;

int main() {
	while(scanf("%d%d", &n,&m)!=EOF) {
		if(!n&&!m) break;
		memset(inDegree,0,sizeof(inDegree));		
		for(int i=0;i<N;i++) e[i].clear();
		while(m--) {
			int a,b;
			scanf("%d%d", &a,&b);
			inDegree[b]++;
			e[a].push_back(b);//要求相同成绩的,编号小的优先 
		}
		while(!q.empty()) q.pop();
		ans.clear();
		
		for(int i=1; i<=n; i++) {
			if(inDegree[i]==0) q.push(i);
		}
		while(!q.empty()) {
			int p=q.top();
			ans.push_back(p);
			q.pop();
			for(int i=0; i<e[p].size(); i++) {
				if(--inDegree[e[p][i]]==0)
					q.push(e[p][i]);
			}
		}
		for(int i=0; i<ans.size()-1; i++)
			printf("%d ", ans[i]);
		printf("%d\n", ans[ans.size()-1]);
	}
	return 0;
}

case3:产生冠军,好题,不过还是这个样例给的好,给出了1->2,3->4这种情况也是没有产生冠军的(虽然依然能进行拓扑排序,但是对于本题来说是失败的)

  • 关键的是,上面那种情况,其实只要判断初始入度为0的node数是否为1即可(这样说明不会出现两个“冠军”没比的情况),接下来就是和case2一样判断排序是否有环即可
  • 因为给的string name,涉及map的应用,判空:mp.count(a)==0,清空可以直接clear
  • string 的scanf:a.resize(20);//是字符,不是字节            
                              scanf("%s", &a[0]);
#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
#include<map>
using namespace std;
const int N=1005;
vector<int>e[N];
int inD[N];
queue<int>q;
map<string,int>mp;//name->id
vector<string>ans;

int main() { //8:52->9:39:37min
	int m;//关系数
	while(scanf("%d", &m)!=EOF, m!=0) {
		int n=0;//人数
		memset(inD,0,sizeof(inD));
		mp.clear();
		for(int i=0; i<N; i++) e[i].clear();
		while(m--) {
			string a,b;
			a.resize(20);//是字符,不是字节 
			b.resize(20);
			scanf("%s%s", &a[0],&b[0]);
			if(mp.count(a)==0)
				mp[a]=n++;
			if(mp.count(b)==0)
				mp[b]=n++;
			e[mp[a]].push_back(mp[b]);
			inD[mp[b]]++;
		}//end input

		while(!q.empty()) q.pop();
		ans.clear();
		int ok=1;//用来判断:不会有2个冠军
		for(int i=0,c=0; i<n; i++) {
			if(inD[i]==0) {
				q.push(i);
				c++;
			}
			if(c>1) {
				printf("No\n");
				ok=0;
				break;
			}
		}
		if(!ok) continue;
		int cnt=0;
		while(!q.empty()) {
			int p=q.front();
			cnt++;
			q.pop();
			for(int i=0; i<e[p].size(); i++) {
				if(--inD[e[p][i]]==0)
					q.push(e[p][i]);
			}
//			cout<<p<<' ';
		}
		printf("%s\n", cnt==n?"Yes":"No");
	}
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值