算法刷题记录(Day 5)

本文通过分析编程竞赛中的题目,探讨了错误的集合数据结构使用(如POJ2388题目中使用set忽略其无重复性质)以及正确的解决策略,如排序和深度优先搜索。此外,还介绍了图论在解决实际问题中的应用,如POJ1724题目中的最短路径问题,提出了深搜剪枝和启发式搜索的方法。最后,详细解析了POJ1094题目中的拓扑排序问题,强调了理解题意和算法的重要性。
摘要由CSDN通过智能技术生成

Who’s in the Middle(poj 2388)

原题链接
方法一:
错误解法:使用set,但是却忽略了set中是没有重复元素的!!!
错误代码如下所示:

#include<iostream>
#include<set>
using namespace std;
#define NMAX 10004
set<int> cow;
int N;
int main() {
	cin >> N;
	for (int i = 0;i < N;i++) {
		int cur;
		cin >> cur;
		cow.insert(cur);
	}
	
	//注意set中的元素是不能够当成数组的元素来进行访问的。
	set<int>::iterator it = cow.begin();
	for (int i = 0;it != cow.end();it++,i++) {
		if (i == N / 2) {
			cout << *it;
			return 0;
		}
	}
}

tips:
1.set的遍历只能通过迭代器
2.set中没有重复元素

方法二:sort排序

#include<iostream>
#include<algorithm>
using namespace std;
#define NMAX 10004
int cow[NMAX];
int N;
int main() {
	cin >> N;
	for (int i = 0;i < N;i++) {
		cin >> cow[i];
	}
	sort(cow, cow+N);
	cout << cow[N / 2];
	//注意set中的元素是不能够当成数组的元素来进行访问的。
	
}

tips:
1.sort的头文件是algorithm
2.sort只有两个参数来标识排序的范围,注意这是一个左闭右开的区间。

补充:常见的七种排序方法
冒泡排序:从左到右相邻的两个数进行比较并移动,一轮过后最右边为最值。
选择排序:一轮遍历后获取最值的位置,并将其交换至其应该属于的位置。
插入排序:将数组无序的第一个数插入数组左边已经形成的有序数列里。
希尔排序:分组进行插入排序,然后最后对结果进行整个的插入排序即可。
快速排序:以基准二分数组,一半比基准大,一半比基准小,进而对每一半再快排。
归并排序:将待排序列分割为多个有序序列,再将两个有序序列归并为一个有序序列,如此重复下去。
堆排序:通过大顶堆或者小顶堆获取最值。

ROADS(poj 1724)

原题链接
问题:
1.如何进行存储?因为在两个城市之间可能存在不同的路
2.思路上是从最短路径开始,看是否满足条件本金的条件吗?
3.题目分类里面是搜索,难道要进行搜索吗?深搜的算法复杂度应该如何分析

针对问题一,我们不应该使用二维数组的方式进行存储,因为这就限制了两个城市之间只能存在一条边,因此,再结合本题是一个有向图的特性我们使用链式前向星的结构来进行存储。

针对问题二,从最短路开始的想法可能存在一定的问题。每次求解最短路后如果再和本金进行比较,不满足条件后再进行次短路的求解。

针对问题三,由于题目中的边多达10000条,因此总觉得深搜会超时,但是细想一下,本题当中点的个数较小,而且有了价格的约束,可以十分方便地进行剪枝,再结合维护的当前已经计算的最小的路径长度进行剪枝,深搜可能会是一种解决方案。
尝试使用深搜来做,那么会造成超时吗?如何来进行相应的复杂度的分析呢?

#include<iostream>
using namespace std;
#define NMAX 102
#define RMAX 10004
#define INF 0x3f3f3f3f
struct Roads {
	int d;
	int l, c;
	int next;
}Rd[RMAX];
int Cy[NMAX];
int V[NMAX];
int K, N, R;
int tot = 0;
int res = INF;
void DFS(int x, int length, int cost) {
	V[x] = 1;//代表已经访问了
	if (cost > K || length> res) goto ret; //当前的总价格大于拥有的本金或者总长度大于目前已经更新的,则直接进行剪枝
	if (x == N) {
		if (length < res) res = length;
		goto ret;
	}

	for (int c = Cy[x];c != -1;c = Rd[c].next) {
		if(!V[Rd[c].d]) DFS(Rd[c].d, length + Rd[c].l, cost + Rd[c].c);
	}
	ret:
	V[x] = 0;
}
int main() {
	cin >> K >> N >> R;

	for (int i = 1;i <= N;i++) Cy[i] = -1, V[i] = 0;

	for (int i = 0;i < R;i++) {
		int S, D, L, T;
		cin >> S >> D >> L >> T;
		Rd[tot].d = D;
		Rd[tot].l = L;
		Rd[tot].c = T;
		Rd[tot].next = Cy[S];
		Cy[S] = tot;
		tot++;
	}
	
	DFS(1, 0, 0);
	/*for (int c = Cy[1];c != 0;c = Rd[c].next) {
		DFS(Rd[c].d, Rd[c].l, Rd[c].c);
	}*/
	if (res == INF) cout << "-1";
	else cout << res;
}

tip:
在这两个条件下,一定不能忘记将x的是否访问标记转变回去。
在这里插入图片描述
其余解法:
spfa+A*的方式进行求解
基于spfa的启发式搜索。将spfa的先进先出队列更改为一个排序队列,首先需要得到各个点到达第N个点的最短距离,以作为代价方程的第二个部分。

代价方程: f = g + h.   1)f:代价。2)g:当前实际已经消耗的代价。3)h:预估的代价(我目前碰到的是先预处理反向建图得到预估代价)

我觉得这种方法类似于深搜,但是它加入了启发式的因素,利用了优先队列,使得结果的到来可能会更加快。

//题解中的代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <queue>
 
#define INF 0x3f3f3f3f
 
using namespace std;
 
struct Node{
	int v, dist, tot, cost; // 当前节点,当前路程,当前路程加上当前节点到终点的最短路,当前花费
	Node() {}
	Node(int a, int b, int c, int d) : v(a), dist(b), tot(c), cost(d) {}
	bool operator < (const Node &n) const { // 优先队列用,每次取出i.dist+d[i]最小的
		if (tot != n.tot)
			return tot > n.tot;
		return dist < n.dist;
	}
};
 
struct Edge{
	int to, v, cost;
	Edge(int a, int b, int c) : to(a), v(b), cost(c) {}
};
 
int n, m, k;
vector<Edge> edges[105];
vector<Edge> redges[105]; // 反向图
int dist[105]; //dist[i]表示从节点i到终点的最短路
int vis[105];
void spfa() { // 求dist数组
	queue<int> q;
	memset(dist, 0x3f, sizeof(dist));
	memset(vis, 0, sizeof(vis));
	vis[n] = 1;
	dist[n] = 0;
	q.push(n);
	while (!q.empty()) {
		int current = q.front();
		q.pop();
		for (int i = 0; i < redges[current].size(); i++) {
			Edge e = redges[current][i];
			if (dist[current] + e.v < dist[e.to]) {
				dist[e.to] = dist[current] + e.v;
				if (!vis[e.to]) {
					vis[e.to] = 1;
					q.push(e.to);
				}
			}
		}
		vis[current] = 0;
	}
}
 
priority_queue<Node> q;
int spfaAstar() {
	while (!q.empty()) q.pop();
	if (dist[1] == 0x3f3f3f3f) return -1;
	Node start = Node(1, 0, dist[1], 0);
	q.push(start);
	while (!q.empty()) {
		Node cur = q.top();
		q.pop();
		if (cur.v == n) { // 如果当前节点为终点
			if (cur.cost <= k) return cur.dist; // 符合条件就return
		}
		for (int i = 0; i < edges[cur.v].size(); i++) {
			Edge e = edges[cur.v][i];
			if (cur.cost + e.cost > k) continue; // 很重要的一条剪枝,当前花费加这条路的花费大于k就减掉。
			q.push(Node(e.to, cur.dist + e.v, cur.dist + e.v + dist[e.to], cur.cost + e.cost)); // 将下一个点加入队列
		}
	}
	return -1;
}
 
int main() {
	while (~scanf("%d %d %d", &k, &n, &m)) {
		int a, b, c, d;
		for (int i = 0; i <= n; i++) {
			edges[i].clear();
			redges[i].clear();
		}
		for (int i = 0; i < m; i++) {
			scanf("%d %d %d %d", &a, &b, &c, &d);
			edges[a].push_back(Edge(b, c, d));
			redges[b].push_back(Edge(a, c, d)); // 反向图
		}
		spfa();
		int ans = spfaAstar();
		printf("%d\n", ans);
	}
	return 0;
}

Sorting It All Out(poj 1094)

原题链接
题目类型:拓扑排序

问题:
题目中的这句话应该如何进行理解呢?这句话暗示了不能仅仅只是在存储了所有的边后才进行排序,而应该每读入一条边,就进行一次拓扑排序,以求得能够出结果的最小的边数。

where xxx is the number of relations processed at the time either a
sorted sequence is determined or an inconsistency is found, whichever
comes first, and yyy…y is the sorted, ascending sequence.

改了很长时间的代码,根本原因在于对于题意和拓扑排序的理解错误。
本题要求的是一种严格的递增关系,而拓扑排序则给出的是一种偏序的关系,这体现在可以同时有多个度为0的点,因此,一方面,拓扑排序可以来判断是否有环的出现,另一方面,需要对于度为0的点进行一个控制,若超过一个,则此次排序是不成功的。
本题一大绕点在于进行一次拓扑排序后应该如何设置返回值的问题上,在这点上我也饶了好久。首先,对于任意一次排序,我们需要判断是否存在环(即矛盾),这是最坏的情况,直接返回0,main程序对于接收到的0,直接输出错误即可;最好的情况是,存在一个完全严格的排序关系,且参与排序的是全体,因此直接返回1,main程序对于接受到1,直接输出排序成功即可;其余情况则输出-1,代表无罪无功,但是当已经将所有的边都考虑在内的时候,此时就有错了,因为代表着没有存在排序。

#include<iostream>
#include<queue> 
#include<vector>
using namespace std;
int n, m;
int map[26][26];
int Du[26];
int Ex[26];
vector<int> res;
int topo() {
	res.clear();
	queue<int> Q;//Q里面每次最多只能有一个
	int du[26];
	int unsure = 0;
	memcpy(du, Du, sizeof(du));
	//找到第一个度为1的元素
	for (int i = 0;i < n;i++) {
		if (!du[i] && Ex[i]) Q.push(i);
	}
	while (!Q.empty()) {
		if (Q.size() > 1) unsure = 1; //不是偏序关系!!!!!
		int p = Q.front();
		Q.pop();
		res.push_back(p);
		for (int i = 0;i < n;i++) {
			if (map[p][i] == 1 && Ex[i]) {
				du[i]--;
				if (du[i] == 0) Q.push(i);
			}
		}
	}
	//首先判断是否出现了环
	for (int i = 0;i < n;i++) {
		//出现了环
		if (du[i] && Ex[i]) return 0;
	}
	
	if (!unsure && res.size()==n) return 1; //有严格的完全排序
	else return -1;

	
}
int main() {
	while (1) {
		cin >> n >> m;
		if (n == 0 && m == 0) break;

		//全部初始化为0
		memset(map, 0, sizeof(map));
		memset(Du, 0, sizeof(Du));
		memset(Ex, 0, sizeof(Du));
		for (int i = 0;i < m;i++) {
			char a, b;
			getchar();
			a = getchar();
			getchar();
			b = getchar();
			Du[b - 'A']++;
			map[a - 'A'][b - 'A'] = 1;
			Ex[a - 'A'] = 1;
			Ex[b - 'A'] = 1;
			int state = topo();
			if (state == 1) {
				//读取后面的内容
				for (int j = i + 1;j < m;j++) {
					getchar();getchar();getchar();getchar();
				}

				cout << "Sorted sequence determined after " << i + 1 << " relations: ";
				for (int j = 0;j < res.size();j++) {
					cout << char('A' + res[j]);
				}
				cout << "." << endl;

				break;
			}
			else if (state == 0) {
				
				//读取后面的内容
				for (int j = i + 1;j < m;j++) {
					getchar();getchar();getchar();getchar();
				}

				cout << "Inconsistency found after " << i + 1 << " relations." << endl;
				break;
			}
			else if(i==m-1) cout << "Sorted sequence cannot be determined." << endl;
		}
	}
}

tip:在进行一次拓扑排序的时候,仅仅对已经出现在题目给出的边中的点进行排序,具体体现在Ex数组上。

小结

理解万岁,既是对于题意,也是对于算法本身。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值