2017中兴算挑

题目

蚁后发布了一项新任务:小蚁同学,我需要玉米库的玉米,再要配点水果,去帮我找来吧。小蚁正准备出发,蚁后又说:哎呀,回来,我还没说完呢,还有若干要求如下:
1. 小蚁同学,你需要尽可能以最少的花费拿到食物((图1中路线上的数值表示每两个储物间的花费));
2. 小蚁同学,你最多只能经过9个储藏间拿到食物(包含起止两个节点,多次通过同一节点按重复次数计算);
3. 小蚁同学,你必须经过玉米间,水果间(图1中标绿色节点);
4. 别忘了,食蚁兽也在路上活动呢,一旦与食蚁兽相遇,性命危矣!不过小蚁微信群 公告已经公布了敌人信息(图1中标红色路段);
5. 最后,千万别忘了,还有两段路是必须经过的,那里有我准备的神秘礼物等着你呢(附件图中标绿色路段)。
设计一种通用的路径搜索算法,来应对各种搜索限制条件,找到一条最优路径,顺利完成蚁后布置的任。
注:
1、蚁巢,有若干个储藏间(图中圆圈表示),储藏间之间有诸多路可以到达。
2、节点本身通行无花费;
3、该图为无向图,可以正反两方向通行,两方向都会计费,并且花费相同;
4、起止节点分别为附件图中S点和E点。
5、最优路径:即满足限制条件的路径。

这里写图片描述

思路

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

代码

#include "iostream"
#include "string"
#include "fstream"
#include "vector"
#include "queue"
#include "sstream"
#include "set"
#include "string.h"
#include "math.h"
#include "time.h"
#include <functional>
using namespace std;
#define INF 200000000
#define N 1000
struct Edge
{
	int to;          // 边终止节点
	int cost;        // 花费
	int flag;        // 1表示必须经过的边,0表示没有要求

	Edge(int to1, int cost1, int flag1)
	{
		to = to1;
		cost = cost1;
		flag = flag1;
	}
};
int nV;                      // 顶点数
int nE;                      // 边数
int nMostV;                  // 经过的定点数上限
int nMustV;                  // 必须经过的顶点数
int nMustE;                  // 必须经过的边数
set<string> mustE;           // 必经边
vector<Edge> G[N];           // 图的邻接表形式
int G1[N][N];                // 图的邻接矩阵形式
int dist[N];                 // 从源点出发的最短距离
bool mustV[N];               // 是否为必经点
vector<int> path;            // 花费最短的路径
int maxE;                    // 边的最大花费
vector<int> V;               // 求解顺序
typedef pair<int, int> P;    // first是最短距离,second是顶点编号
vector<Edge> G2[N];          // 用于边权为1的图
int G3[N][N];                // 用于边权为1的图
vector<int> path1;           // 顶点数最短的路径
int preV[N];                 // 前向节点数组
bool vis[N];                 // 当前节点是否访问过
vector<Edge> G4[N];          // 用于求2点之间所有最短路径
// 一个路径结果
struct Ans
{
	vector<int> path;        // 路径
	int cost;                // 花费
	int start;               // 起始点

	void getCost()
	{
		cost = G1[start][path[0]];   // 实际花费
		for(int i=0; i<path.size()-1; i++)
		{
			cost += G1[path[i]][path[i+1]];
		}
	}
};
void build();                                                   // 建图
void dijkstra(int s, vector<Edge> G[N]);                        // 2点之间的所有最短路径
void dijkstra(int s, vector<Edge> G[N], int *preV);             // 2点之间的1条最短路径
void findPath(int s, int t, int *preV, vector<int> &path);      // 寻找s到t之间的路径,并将其加入总路径path
void subSolve(vector<Edge> G[N], int *preV, vector<int> &path); // 寻找全局路径
void printPath(vector<int> &path, bool end);                    // 打印路径
void solve();                                                   // 主体
void solve1();                                                  // 寻找满足条件的路径
void addEdge(int from, int to, int cost, int flag, vector<Edge> G[N])
{
    Edge e(to, cost, flag);
    G[from].push_back(e);

	Edge e1(from, cost, flag);  
    G[to].push_back(e1);
}
void build()
{
	int i;
	ifstream fin;
	fin.open("500点图7.txt");
	cout << "顶点数:";
	fin >> nV; 
	cout << nV << endl;
	cout << "边数:";
	fin >> nE; 
	cout << nE << endl;
	cout << "经过点的上限:";
	fin >> nMostV;
	cout << nMostV << endl;
	cout << "必经点(第一行为总数):" << endl;
	fin >> nMustV; 
	cout << nMustV << endl;
	int v = 0;
	memset(mustV, false, sizeof(mustV));
	for(i=0; i<nMustV; i++)
	{
		fin >> v;
		cout << v << " ";
		mustV[v] = true;
	}
	int nMustE = 0;
	int nLimitE = 0;
	// 输入图
	for(i=0; i<nV; i++)
	{
		for(int j=i; j<nV; j++)
		{
			G1[i][j] = G1[j][i] = INF;
			G3[i][j] = G3[j][i] = INF;
		}
	}
	cout << endl << "图{边的起点,终点,花费,类型(1表示必经点,-1表示禁止经过的点,0表示无要求)}:";
	cout << "略..." << endl;
	int from, to, cost, flag;
	for(i=0; i<nE; i++)
	{
		fin >> from >> to >> cost >> flag;
		//cout << from << " " << to << " " << cost << " " << flag << endl;
		if(flag != -1)  // 如果不是禁止经过的边
		{
			addEdge(from, to, cost, flag, G);
			G1[from][to] = G1[to][from] = cost;
			addEdge(from, to, 1, flag, G2);
			G3[from][to] = G3[to][from] = 1;
		}
		if(flag == 1)
		{
			nMustE++;
			mustV[from] = mustV[to] = true;
			stringstream sin, sin1;
			sin << from << "," << to;
			mustE.insert(sin.str());
			sin1 << to << "," << from;
			mustE.insert(sin1.str());
		}
		else if(flag == -1)
			nLimitE++;
	}
	cout << "必经边数:" << nMustE << endl;
	cout << "禁止边数:" << nLimitE << endl;
	fin.close();
}
// 求2点之间的所有最短路径
void dijkstra(int s, vector<Edge> G[N])
{
	fill(dist, dist + nV, INF);
	priority_queue<P, vector<P>, greater<P> > q;
	dist[s] = 0;
	q.push(P(0, s));
	while(!q.empty())
	{
		P p = q.top();   //从尚未使用的顶点中找到一个距离最小的顶点
		q.pop();
		int v = p.second;
		if(dist[v] < p.first)
			continue;
		for(int i=0; i<G[v].size(); i++)  
		{
			Edge &e = G[v][i];
			int dis = dist[v] + e.cost;
			if(dist[e.to] > dis)
			{
				dist[e.to] = dist[v] + e.cost;
				q.push(P(dist[e.to], e.to));
				G4[v].push_back(e);
			}
			else if(dist[e.to] == dis)
			{
				G4[v].push_back(e);
			}
		}
	}
}
// 求2点之间的1条最短路径
void dijkstra(int s, vector<Edge> G[N], int *preV)
{
	fill(dist, dist + nV, INF);
	priority_queue<P, vector<P>, greater<P> > q;
	dist[s] = 0;
	q.push(P(0, s));
	while(!q.empty())
	{
		P p = q.top();   //从尚未使用的顶点中找到一个距离最小的顶点
		q.pop();
		int v = p.second;
		if(dist[v] < p.first)
			continue;
		for(int i=0; i<G[v].size(); i++)  
		{
			Edge &e = G[v][i];
			int dis = dist[v] + e.cost;
			if(dist[e.to] > dis)
			{
				dist[e.to] = dist[v] + e.cost;
				q.push(P(dist[e.to], e.to));
			}
		}
	}
}
// 寻找s到t之间的路径,并将其加入总路径path
void findPath(int s, int t, int *preV, vector<int> &path)
{
	vector<int> tmp;
	for(int i=t; i != s; i = preV[i])
	{
		tmp.push_back(i);
	}
	for(int k=tmp.size()-1; k>=0; k--)
	{
		path.push_back(tmp[k]);
	}
}
// 用于搜2点之间的多条最短路径
void dfs(int s, int t, Ans &A, vector< Ans > &paths, int start, int &min)
{
	if (s == t)
	{
		A.start = start;
		A.getCost();
		if(A.path.size() < min)
			min = A.path.size();
		paths.push_back(A);
	}
	if(A.path.size() < min)
	{
		for (int i = 0; i < G4[s].size(); i++)
		{
			int u = G4[s][i].to;
			if (!vis[u])
			{
				vis[u] = true;
				A.path.push_back(u);
				dfs(u, t, A, paths, start, min);
				A.path.pop_back();
				vis[u] = false;
			}
		}
	}
}
void subSolve(vector<Edge> G[N], int *preV, vector<int> &path)
{
	// 求无约束下的单源最短路径,用于得到必经点的顺序
	dijkstra(0, G, preV);   
	// 得到必经点的顺序
	if(V.empty())
	{
		priority_queue< P, vector<P>, greater<P> > q1;
		int i;
		for(i=0; i<nV; i++)
		{
			if(mustV[i])
			{
				q1.push(P(dist[i], i));
			}
		}
		V.push_back(0);
		cout << endl;
		while(!q1.empty())
		{
			P p = q1.top();
			q1.pop();
			V.push_back(p.second);
		}
		V.push_back(nV - 1);
	}
	// 必经点顺序微调
	// 将必经点中属于同一必经边的2个点放在一起
	int n = V.size();
	int i;
	for(i=0; i<n-2; i++)
	{
		for(int j=i+2; j<n; j++)
		{
			stringstream sin;
			sin << V[i] << "," << V[j];
			if(mustE.count(sin.str()))   // 如果是必经边
			{
				int tmp = V[j];
				for(int k=j; k>i+1; k--)
					V[k] = V[k-1];
				V[i+1] = tmp;
				break;
			}
		}
	}
	// 求解每对必经点之间的最短路径,合并为最终解
	int last = V[0];
	i = 1;
	while(i < V.size())
	{
		int cur = V[i++];

		stringstream sin;
		sin << last << "," << cur;
		if(mustE.count(sin.str()))
		{
			path.push_back(cur);
			last = cur;
			cur = V[i++];
		}
		// 修正的dijkstra算法,用于求2点之间所有最短路径
		dijkstra(last, G); 
		// 搜寻花费最少的多条路径,用到剪枝
		vector<Ans> paths;
		Ans ans;
		memset(vis, false, sizeof(vis));
		int cnt = INF;
		dfs(last, cur, ans, paths, last, cnt);  
		for(int v=0; v<nV; v++)
			G4[v].clear();
		// 选取花费最小且节点数最少的路径
		int min = paths[0].cost;
		int minId = 0;
		for(int k=1; k<paths.size(); k++)
		{
			for(int j=0; j<paths[k].path.size(); j++)
			{
				if(paths[k].cost < min)
				{
					min = paths[k].cost;
					minId = k;
				}
			}
		}
		for(int j=0; j<paths[minId].path.size(); j++)  
		{
			path.push_back(paths[minId].path[j]);
		}
		last = cur;
	}
}
void printPath(vector<int> &path, bool end)
{
	int cost = 0;
	cout << "0-->";
	cost += G1[0][path[0]];
	for(int i=0; i<path.size()-1; i++)
	{
		cout << path[i] << "-->";
		cost += G1[path[i]][path[i+1]];
		if(G1[path[i]][path[i+1]] == INF)
			cout << " " << path[i] << ", " << path[i+1] << endl;
	}
	if(end)
	{	
		cost += G1[0][path[0]];
		cout << path[path.size()-1] << "-->";
		if(G1[0][path[0]] == INF)
			cout << " " << 0 << ", " << path[0] << endl;
	}
	cout << nV-1 << endl;
	cout << endl << "花费:" << cost << endl << endl;
}
// 搜s到t的多条路径
void dfs(int s, int t, Ans &A, int n, vector< Ans > &paths, int start)
{
	if (s == t)
	{
		A.start = start;
		A.getCost();
		paths.push_back(A);
	}
	if (A.path.size() < n)
	{
		for (int i = 0; i < G[s].size(); i++)
		{
			int u = G[s][i].to;
			if (!vis[u])
			{
				vis[u] = true;
				A.path.push_back(u);
				dfs(u, t, A, n, paths, start);
				A.path.pop_back();
				vis[u] = false;
			}
		}
	}
}
// 求每对必经点之间满足约束的路径
// 即当花费最少不满足时,退而求其次,求一个花费较大,但满足节点上限的路径
void solve1()
{
	int cnt = 0;
	int cnt1 = 0;
	int i = 0;
	int i1 = 0;
	int n = path.size();
	int n1 = path1.size();
	vector<int> path2;
	int last = V[0];
	int k = 1;
	while (k < V.size())
	{
		bool flag = true;
		int cur = V[k++];

		stringstream sin;
		sin << last << "," << cur;
		if (mustE.count(sin.str()))
		{
			path2.push_back(last);
			path2.push_back(cur);
			last = cur;
			cur = V[k++];
			i += 2;
			i1 += 2;
			flag = false;
		}

		cnt = cnt1 = 0;
		vector<int> tmp;
		while(i<n && path[i] != cur)
		{
			tmp.push_back(path[i]);
			i++;
			cnt++;
		}
		while(i1<n1 && path1[i1] != cur)
		{
			i1++;
			cnt1++;
		}
		if(cnt > cnt1)
		{
			vector<Ans> paths;
			Ans ans;
			memset(vis, false, sizeof(vis));
			dfs(last, cur, ans, cnt1+1, paths, last);
			if (paths.size() > 0) 
			{
				int minId = 0;
				int minCost = paths[0].cost;
				int n = paths.size();
				int j;
				for (j = 1; j < n; j++)
				{
					int cost = paths[j].cost;
					if (paths[j].cost < minCost)
					{
						minId = j;
						minCost = paths[j].cost;
					}
				}

				if(flag)
					path2.push_back(last);
				for (j = 0; j < paths[minId].path.size()-1; j++)
				{
					path2.push_back( paths[minId].path[j] );
				}
			}
		}
		else
		{
			for(int k=0; k<tmp.size(); k++)
			{
				path2.push_back(tmp[k]);
			}
		}
		last = cur;
	}
	if(path2.size()+2 <= nMostV)
	{
		cout << endl << "存在符合条件的路径:\n";
	}
	else
	{
		cout << endl << "不存在符合条件的路径,给出次优路径(去掉点数上限约束):\n";
	}
	printPath(path2, true);
}
void solve()
{
	build();
	subSolve(G, preV, path);         // 得到满足约束,且花费少的路
	int n = path.size();

	if(n+1 <= nMostV)                  // 没有超过点数限制
	{
		cout << endl << "存在符合条件的路径:\n";
		printPath(path, false);      // 打印最优解
	}
	else                             // 超过点数限制
	{
		subSolve(G2, preV, path1);   // 得到满足约束,且节点数少的路径
		int n1 = path1.size();

		if(n1+1 > nMostV)            // 点数最少时也会超过顶点上限,不存在符合条件的解
		{
			cout << endl << "不存在符合条件的路径,给出次优路径(去掉点数上限约束):\n";
			printPath(path, false);   
		}
		else                          // 可能存在最优路径
		{
			solve1();
		}
	}
}
int main()
{
	clock_t start, finish;
	start = clock();
	solve();
	finish = clock();
	cout << "用时:" << (double)(finish-start) / CLOCKS_PER_SEC << " s" << endl;
	getchar();
	return 0;
}

这里写图片描述
DearEidolon 提出了如下修改,暂未验证,路过的同学帮忙验证一下~

void dijkstra(int start, vector<Edge> GList[N])
{
	fill(minDis, minDis + nV + 1, INF);
	priority_queue<P, vector<P>, greater<P> > q;
	minDis[start] = 0;
	q.push(P(minDis[start], start));
	
	vector<markStruct> markVector;
	markStruct markstruct(P(minDis[start],start), start, Edge(start, 0));
	markVector.push_back(markstruct);
	
	while (!q.empty())
	{
		P p = q.top();   //从尚未使用的顶点中找到一个距离最小的顶点
		q.pop();
		int v = p.second;
		if (minDis[v] < p.first)
			continue;
		for (int i = 0; i<GList[v].size()
			Edge &e = GList[v][i];
		int dis = minDis[v] + e.cost;
		if (minDis[e.to] > dis)
		{
			if (minDis[e.to] != INF)  //如果起始点到e.to的最近距离minDis[e.to]不是第一次更新,需要将上次放入GList的e删掉
				for (vector<markStruct>::iterator iter1 = markVector.begin(); iter1 != markVector.end(); iter1++)
					if (iter1->PreminDisTo == P(minDis[e.to], e.to))
						for (vector<Edge>::iterator iter2 = GListSel[iter1->TempStartV].begin(); iter2 != GListSel[iter1->TempStartV].end(); )
							if (iter2->cost == iter1->SelE.cost&&iter2->to == iter1->SelE.to)
								iter2 = GListSel[iter1->TempStartV].erase(iter2);
							else
								++iter2;
			minDis[e.to] = dis;
			q.push(P(minDis[e.to], e.to));
			GListSel[v].push_back(e);
	
			markStruct markstruct(P(minDis[e.to], e.to), v, e); //每次放入GList一个e,就要标记一次这个e对应的P和v
			markVector.push_back(markstruct);
		}
		else if (minDis[e.to] == dis)
		{
			GListSel[v].push_back(e);
			markStruct markstruct(P(minDis[e.to], e.to), v, e); //每次放入GList一个e,就要标记一次这个e对应的P和v
			markVector.push_back(markstruct);
		}
		q = priority_queue<P, vector<P>, greater<P> >();  //做一次dijkstra算法,清空一次最短距离容器q
		markVector.swap(vector<markStruct>());            //做一次dijkstra算法,清空一次标记容器markVector
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值