[蓝桥杯][算法提高VIP]士兵排队问题

说在前面,这题没标明要“对于位置多种选择的字母尽量保证按字典序排序”


更新于2022.4.3

上面说的也不是很对,比如样例,显然ABFD是字典序更小的合法方案,但是答案却要求输出AFBD,这样上面的说法也就不合理了(自己否定原来的自己)

下面的AC代码对于我下面图中给出的“是拓扑”中的最后一个样例的测试输出始终不是ABCDE,也就是说并不是输出的字典序最小的,反正确实AC了,只能说这道题当个笑话看看吧。至于如果要按照字典序最小的方式输出的话,我最下面补充了一段代码。


题目

题目链接

题解

拓扑排序。


太坑了,你要是用的模板是入度自减为零后直接入队的,那么你是最多过64%的数据;
经过我严密的测试,发现必须满足“那些位置不固定的字母,要尽量按字典序排列”,也就是由同一个结点扩展出来的入度自减后变为零的结点,ASCII码小的要先入队才行。这点是题目没提到的。
所以,如果广大同学搜到本篇博客算你走运,我找了一小时题解,没有一篇研究过,都是没法AC的代码,只有此链接下网站内的一篇题解AC了,仔细研究了仨小时后得出结论,蓝桥杯傻逼。更准确的说应该是这个网站傻逼,因为网上搜到的虽然都没有AC,但是他们应该是在蓝桥官网上提交的,因此可以AC,但是这个网站不行。(用着人家免费的题,居然还骂人家,就骂这一次,下次一定不)


进入正题

什么是拓扑?
看个图吧,看定义感觉理解的还是不会到位。
在这里插入图片描述
存在环了就不存在拓扑序了。存在先后关系,就存在拓扑序。

拓扑排序具有以下几点(个人总结所以比较没逻辑):

  1. 拓扑排序是广搜的应用
  2. 拓扑排序是针对有向图来说,无向图没有拓扑序列
  3. 有向无环图一定存在拓扑序列(有向无环图也被称作拓扑图)
  4. 与入度有关
  5. 入度为0的点作为拓扑序的起点
  6. 一个图的拓扑序不一定唯一

拓扑模板:

将入度为0的点入队
while(队列不空) {
	取出队头t
	枚举队头t的所有出边(出边为t -> j)
		t的入度--(删除t -> j)
		判断t的入度是否为0,若为0则将t加入队列
}

代码模板:

// 先将入度为0的结点入队,同时保存在ans中
while(!q.empty()) {
		int t = q.front();
		q.pop();
		for(int i = h[t];i != -1;i = ne[i]) {
			int j = e[i];
			--d[j];
			if(!d[j]) q.push(j), ans[++sum] = j;
		}
	}
}
// 其实入队顺序(或者出队顺序)就是一个拓扑序

一般使用邻接表实现。

邻接表的相关内容自行百度吧,这里我只给出模板:

// N为点个数, M为边个数
int h[N], e[M], ne[M], idx;
void add(int a, int b) {
	e[idx] = b; ne[idx] = h[a]; h[a] = idx ++;
}
// 遍历
// u表示编号为u的点
for(int i = h[u];i != -1;i = ne[i]) { // 其实遍历的是idx
	int v = e[i]; // u所连结点的编号	
	// ...
}
// 初始化
memset(h, -1, sizeof h);

邻接表是为每个点都开一个单链表,表示与每个点相连接的点有哪些。
idx为每条边的编号。
h[a]表示编号为a的点与其他点连边的编号。
e[idx]表示idx这条边终点编号。
ne[idx]表示idx这条边的下一条边的idx

AC代码1(适合于知道优先队列的同学)

#include<bits/stdc++.h>
using namespace std;
const int N = 30, M = 1010;

int e[M], ne[M], h[N], idx;
char s[N];
int only[N][N], vis[N], ans[N], in[N];
int sum, cnt;
queue<int> q;
priority_queue<int, vector<int>, greater<int> > pq; 

void add(int a, int b) {
	e[idx] = b; ne[idx] = h[a]; h[a] = idx ++;
}

int main() {
	
	memset(h, -1, sizeof h);
	while(~scanf("%s", s)) {
		int a = s[0]-64, b = s[2]-64;// a:1  b:2  c:3  ……
		if(!only[a][b]) { // 用于防止一条边多次输入影响入度,好像给的数据并没有这样的,这只是以防万一
			add(a, b);
			in[b] ++; // 入度
			only[a][b] = 1;
			if(!vis[a]) cnt++, vis[a] = 1; // vis[i]表明字母i是不是要进行排队,即字母i是不是出现过
			if(!vis[b]) cnt++, vis[b] = 1;
		}
	}
	for(int i = 1;i <= 26;i ++) if(vis[i] && !in[i]) q.push(i); // 一开始入度为0的点入队
		
	while(!q.empty()) { 
		int t = q.front();
		q.pop();
		ans[++sum] = t; // 按出队顺序统计
		
		for(int i = h[t];i != -1;i = ne[i]) {
			int j = e[i];
			--in[j];
			if(!in[j]) pq.push(j);
		}
		
		while(!pq.empty()) {
			int t = pq.top();
			pq.pop();
			q.push(t); // 按字典序入队
		}
	}
	if(cnt == sum) for(int i = 1;i <= sum;i ++) cout << (char) (ans[i]+64);
	else puts("No Answer!");
}

AC代码2(适合全体同学,但代码比较冗余)

#include<bits/stdc++.h>
using namespace std;
const int N = 30, M = 1010;

int e[M], ne[M], h[N], idx;
char s[N];
int only[N][N], vis[N], ans[N], in[N], st[N];
int sum, cnt;
queue<int> q;

void add(int a, int b) {
	e[idx] = b; ne[idx] = h[a]; h[a] = idx ++;
}

int main() {
	
	memset(h, -1, sizeof h);
	while(~scanf("%s", s)) {
		int a = s[0]-64, b = s[2]-64;// a:1  b:2  c:3  ……
		if(!only[a][b]) {// 用于防止一条边多次输入影响入度,好像给的数据并没有这样的,这只是以防万一
			add(a, b);
			in[b] ++;// 入度
			only[a][b] = 1;
			if(!vis[a]) cnt++, vis[a] = 1;// vis[i]表明字母i是不是要进行排队,即字母i是不是出现过
			if(!vis[b]) cnt++, vis[b] = 1;
		}
	}
	for(int i = 1;i <= 26;i ++) if(vis[i] && !in[i]) q.push(i);// 一开始入度为0的点入队
		
	while(!q.empty()) { 
		int t = q.front();
		q.pop();
		ans[++sum] = t;// 按出队顺序统计
		
		for(int i = h[t];i != -1;i = ne[i]) {
			int j = e[i];
			--in[j];
		}
		
		for(int i = 1;i <= 26;i ++) { // 二十六个字母依次遍历就可以将字典序小的且满足入度为0的先入队
			if(vis[i]) { // 筛选出要排队的字母有哪些
				if(!in[i] && !st[i]) { // 入度为0并且没有入过队
					st[i] = 1;
					q.push(i);
				}
			}
		}
	}
	if(cnt == sum) for(int i = 1;i <= sum;i ++) cout << (char) (ans[i]+64);
	else puts("No Answer!");
}

WA代码3(标准模板,但是不满足按字典序)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2100, M = 30;

int a, b, d[M], ans[M], cnt, sum, vis[M], flag[M][M];
int idx, ne[N], h[M], e[N];
char s[N];
queue<int> q;


void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

int main()
{
	idx = 0, sum = 0, cnt = 0;
	memset(h, -1, sizeof h);
	memset(vis, 0, sizeof vis);
	memset(flag, 0, sizeof flag);
	memset(d, 0, sizeof d);
	while(scanf("%s", s)!=EOF) {
		int a = s[0]-64, b = s[2]-64;
		if(!flag[a][b]) {
			d[b] ++, add(a, b); // a>b : a->b
			flag[a][b] = 1;
		}
		vis[a] = vis[b] = 1;
	}
	
	for(int i = 1;i <= 26;i ++) {
		cnt += vis[i];
		if(vis[i] && !d[i]) q.push(i), ans[++sum] = i;
	}
	
	while(!q.empty()) {
		int t = q.front();
		q.pop();
		for(int i = h[t];i != -1;i = ne[i]) {
			int j = e[i];
			--d[j];
			if(!d[j]) q.push(j), ans[++sum] = j;
		}
	}
	if(sum == cnt) for(int i = 1;i <= cnt;i ++) cout << char(ans[i]+64);
	else puts("No Answer!");
	
	return 0;
}

输出字典序最小的方案

#include<bits/stdc++.h>
using namespace std;
const int N = 1000;

int n = 26;
int d[N], e[N][N], st[N];

vector <int> ans;
priority_queue<int, vector<int>, greater<int> > q; 


bool topo () {
	for (int i = 0;i < n;i ++) 
		if (!d[i]) q.push (i);
	
	while (!q.empty ()) {
		int t = q.top ();
		q.pop ();
		ans.push_back (t);
		
		for (int i = 0;i < n;i ++) {
			if (e[t][i]) {
				d[i] --;
				if (!d[i]) q.push (i);
			}
		}
	}
	return ans.size() == n;
}

int main() {
	
	string s;
	while (cin >> s) {
		int a = s[0] - 65;
		int b = s[2] - 65;
		d[b] ++;
		st[a] = st[b] = 1;
		e[a][b] = 1; // 也可以用add邻接表
	}		
	
	if (topo ()) {
		for (int i = 0;i < ans.size();i ++)
			if (st[ans[i]]) cout << char (ans[i] + 65);
	} else {
		puts ("No Answer!");
	}
	
	return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不牌不改

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值