城市旅游购物交通咨询模拟(最小环floyd法/dp)

题目摘要

城市旅游购物交通咨询模拟
【问题描述】
沈阳城内有若干旅游观光景点和商业区。游客主要以公交车为交通工具出游。假设往返于每个景点和商业区的公交线路不少于6路。旅客希望中转次数最少、时间最短、费用最省。
【设计要求】
设计城市交通咨询模拟程序。
(1)采用图结构、集合等数据结构。
(2)可以随机、文件及人工输入数据。
(3)可以完成旅游、购物一日游的最佳线路。
(4)可以统计数据并满足必要的约束条件。
(5)可以查询和更新数据。
(6)其它完善性或扩展性功能。

思路
此题和URAL - 1004的题目类似,可以按照两种方法,由于是普通的课设,我就按照的是floyd 法来写的,不过需要注意保存路径,以及求最小环的具体算法部分。

Floyd法


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>

using namespace std;
const int INF = 1 << 28;
const int maxn = 100 + 10;
/**/

int n, m;
int ans, num,sum[2],ans1,num1,sum1[2],num2,sum2;
int g[maxn][maxn], dis[maxn][maxn],cost[maxn][maxn],g1[maxn][maxn];
int path[maxn],path1[maxn],pre[maxn][maxn],pre1[maxn][maxn],pre2[maxn][maxn];
int path2[maxn];

void init(int flag,int t) {
	FILE *fp;
	if(flag) {
		if(t==1) fp=fopen("input.txt","r");
		if(t==2) fp=fopen("rand.txt","r");
	}
	n=m=1;
	printf("请输入城市景点数和交通线路数量:\n");
	if(!flag) {
		do {
			if(n<=0||m<=0) printf("请重新输入正确的值:\n");
			scanf("%d %d", &n,&m);
		} while(n<=0||m<=0);
	} else fscanf(fp,"%d %d", &n,&m);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++) {
			if (i == j) g1[i][j] = g[i][j] = dis[i][j] = cost[i][j] = 0;
			else g1[i][j] = g[i][j] = dis[i][j] = cost[i][j] = INF;
			pre[i][j] = pre1[i][j] =pre2[i][j]= i;
		}
	printf("请输入交通线路(线路预估时间和目的地景点门票价格):\n");
	for (int i = 0; i < m; i++) {
		int u, v, w, c;
		if(!flag) scanf("%d %d %d %d", &u, &v, &w, &c);
		else fscanf(fp,"%d %d %d %d", &u, &v, &w, &c);
		if (dis[u][v] > w) {
			g[u][v] = g[v][u] = dis[u][v] = dis[v][u] = w;
		}
		if (cost[u][v] > c) {
			g1[u][v] = g1[v][u] = cost[u][v] = cost[v][u] = c;
		}
	}
	printf("读取成功!\n");
	if(flag) fclose(fp);
}

void randd() {
	FILE *fp;
	fp=fopen("rand.txt","w");
	int a=5,b=20;
	int c=(rand() % (b-a+1)) + a;
	a=c,b=c*(c-1);
	int d=(rand() % (b-a+1)) + a;
	fprintf(fp,"%d %d\n",c,d);
	for(int i = 0; i<d; i++)
		fprintf(fp,"%d %d %d %d\n",(rand() % c) + 1,(rand() % c) + 1,(rand() % (300-10+1)) + 10,(rand() % (300-10+1)) + 10);
	fclose(fp);
	init(1,2);
}

void floyd(int s,int t) {
	sum2=0;
	num2=0;
	for (int k = 1; k <= n; k++) {
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++) {
				if (dis[i][j] > dis[i][k] + dis[k][j]) {
					dis[i][j] = dis[i][k] + dis[k][j];
					pre2[i][j] = pre2[k][j];
				}
			}
	}
	if(dis[s][t]<INF&&dis[s][t]) {

		int p = t;
		while (p != s) {
			path2[num2++] = p; //记录路径
			p = pre2[s][p];
		}
		path2[num2++] = s;
		for(int i=num2-1; i>=0; i--) {
			printf("%d ",path2[i]);
		}
		for (int i = 0; i < num2-1; i++) {
			sum2+=g[path2[i]][path2[i+1]];
		}
		puts("");
		printf("总时间为%d          中转次数为:%d\n",sum2,num2);
	} else printf("抱歉,没有合适的路线。\n");
}

void floyd1() {
	ans = INF;
	for (int k = 1; k <= n; k++) {
		for (int i = 1; i < k; i++)
			for (int j = i + 1; j < k; j++) {
				int tmp = dis[i][j] + g[i][k] + g[k][j];  //从k点出发,回到k点
				if (tmp < ans) {
					ans = tmp;
					num = 0;
					int p = j;
					while (p != i) {
						path[num++] = p; //记录路径
						p = pre[i][p];
					}
					path[num++] = i;
					path[num++] = k;
				}
			}
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++) {
				if (dis[i][j] > dis[i][k] + dis[k][j]) {
					dis[i][j] = dis[i][k] + dis[k][j];
					pre[i][j] = pre[k][j];
				}
			}
	}
	int p[maxn];
	for (int i = 0; i < num; i++) {
		p[i]=path[i];
	}
	sum[0]=sum[1]=0;
	for (int i = 0; i < num-1; i++) {
		sum[0]+=g[p[i]][p[i+1]];
		sum[1]+=g1[p[i]][p[i+1]];
	}
	sum[0]+=g[p[num-1]][p[0]];
	sum[1]+=g1[p[num-1]][p[0]];
}

void floyd2() {
	ans1 = INF;
	for (int k = 1; k <= n; k++) {
		for (int i = 1; i < k; i++)
			for (int j = i + 1; j < k; j++) {
				int tmp = cost[i][j] + g1[i][k] + g1[k][j];  //从k点出发,回到k点
				if (tmp < ans1) {
					ans1 = tmp;
					num1 = 0;
					int p = j;
					while (p != i) {
						path1[num1++] = p; //记录路径
						p = pre1[i][p];
					}
					path1[num1++] = i;
					path1[num1++] = k;
				}
			}
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++) {
				if (cost[i][j] > cost[i][k] + cost[k][j]) {
					cost[i][j] = cost[i][k] + cost[k][j];
					pre1[i][j] = pre1[k][j];
				}
			}
	}
	int p[maxn];
	for (int i = 0; i < num1; i++) {
		p[i]=path1[i];
	}
	sum1[0]=sum1[1]=0;
	for (int i = 0; i < num1-1; i++) {
		sum1[0]+=g[p[i]][p[i+1]];
		sum1[1]+=g1[p[i]][p[i+1]];
	}
	sum1[0]+=g[p[num1-1]][p[0]];
	sum1[1]+=g1[p[num1-1]][p[0]];
}


void ConsultMenu() { /*咨询系统菜单*/

	printf("\n\n");
	printf("************************欢迎进入咨询系统**********************\n");
	printf("*                       1.咨询最省钱一日游路线               *\n");
	printf("*                       2.咨询最短时间一日游路线             *\n");
	printf("*                       3.咨询最快路线                       *\n");
	printf("*                       0.退出                               *\n");
	printf("**************************************************************\n");
	printf("\n\n");
}

void ManageMenu() {  /*管理系统菜单*/

	printf("\n\n");
	printf("************************欢迎进入管理系统**********************\n");
	printf("*                       1.初始化线路                         *\n");
	printf("*                       2.更新线路                           *\n");
	printf("*                       3.删除线路                           *\n");
	printf("*                       4.查询线路                           *\n");
	printf("*                       5.从文件读取线路                     *\n");
	printf("*                       6.生成随机线路(供测试)             *\n");
	printf("*                       0.退出                               *\n");
	printf("**************************************************************\n");
	printf("\n\n");
}

void MainMenu() {      /*主菜单*/

	printf("\n\n");
	printf("**************欢迎使用城市旅游购物交通咨询模拟系统************\n");
	printf("*                       1.管理系统                           *\n");
	printf("*                       2.咨询系统                           *\n");
	printf("*                       0.退出                               *\n");
	printf("**************************************************************\n");
	printf("\n\n");

}

void Change() { //更新线路
	printf("请输入起始点,更改后的线路预估时间和目的地景点门票价格:\n");

	int u, v, w, c;
	scanf("%d%d%d%d", &u, &v, &w, &c);
	g[u][v] = g[v][u] = dis[u][v] = dis[v][u] = w;
	g1[u][v] = g1[v][u] = cost[u][v] = cost[v][u] = c;
	printf("更新成功!\n");
}

void Del() { //删除线路
	int u,v;
	printf("请输入起始点:\n");
	scanf("%d%d", &u, &v);
	g[u][v] = g[v][u] = dis[u][v] = dis[v][u] = INF;
	g1[u][v] = g1[v][u] = cost[u][v] = cost[v][u] = INF;
	printf("删除成功!\n");
}


void Find() { //查询某条线路
	int u,v;
	printf("请输入起始点:\n");
	scanf("%d%d", &u, &v);
	if((g[u][v]^INF&&g[u][v])||(g[v][u]^INF&&g[v][u])) {
		printf("线路信息为:\n");
		printf("起始点    终止点    预估时间     价格:\n");
		printf("%d        %d        %d           %d  :\n", u, v, g[u][v], g1[u][v]);
	} else {
		printf("Sorry, there is no such route by bus.\n");
	}
}

void ManageSystem() {/*管理系统*/
	int flag = 1;
	while(flag) {
		system("cls");
		ManageMenu();
		int choice=0;
		printf("请选择要执行的操作:\n");
		scanf("%d",&choice);
		switch(choice) {
		case 1:
			init(0,0);
			system("pause") ;
			break;
		case 2:
			Change();
			system("pause") ;
			break;
		case 3:
			Del();
			system("pause") ;
			break;
		case 4:
			Find();
			system("pause") ;
			break;
		case 5:
			init(1,1);
			system("pause") ;
			break;
		case 6:
			randd();
			system("pause") ;
			break;
		case 0:
			flag = 0;
			break;
		default:
			printf("请输入正确的序号。\n");
			system("pause");
		}
	}
}

void ConsultSystem() { /*咨询系统*/
	int flag = 1;
	while(flag) {
		system("cls");
		ConsultMenu();
		int choice=0;
		printf("请选择要咨询的路线方式:\n");
		scanf("%d",&choice);
		switch(choice) {
		case 1:      //咨询最省钱一日游路线
			floyd2();
			if (ans1 >= INF) printf("No solution.\n");
			else {
				printf("以下是最省钱一日游路线,您可以选择最近的点开始您的旅游!\n");
				for (int i = 0; i < num1; i++) printf("%d ", path1[i]);
				printf("  总时间为%d        总费用为%d          中转次数为:%d\n",sum1[0],sum1[1],num1);
			}
			system("pause");
			break;
		case 2:
			floyd1();
			if (ans >= INF) printf("No solution.\n");
			else {
				printf("以下是最省时间一日游路线,您可以选择最近的点开始您的旅游!\n");
				for (int i = 0; i < num; i++) printf("%d ", path[i]);
				printf("  总时间为%d        总费用为%d          中转次数为:%d\n",sum[0],sum[1],num);
			}
			system("pause");
			break;
		case 3:
			printf("请输入起点和终点:\n");
			int x,y;
			do {
				if(x<=0||x>m||y<=0||y>n) printf("请重新输入正确的值:\n");
				scanf("%d %d", &x,&y);
			} while(x<=0||x>m||y<=0||y>n);
			floyd(x,y);
			system("pause");
			break;
		case 0:
			flag = 0;
			break;
		default:
			printf("请输入正确的序号。\n");
			system("pause");
		}
	}
}

int main() {
	srand((int)time(0));
	int flag = 1;
	while(flag) {
		system("cls");
		int choice;
		MainMenu();
		printf("请选择要进入的系统:");
		scanf("%d",&choice);
		switch(choice) {
		case 1:
			ManageSystem();
			break;
		case 2:
			ConsultSystem();
			break;
		case 0:
			flag = 0;
			break;
		default:
			printf("请输入正确的序号。\n");
			system("pause");
		}
	}
	return 0;
}
/*游客主要以公交车为交通工具出游。
假设往返于每个景点和商业区的公交线路不少于6路。
旅客希望中转次数最少、时间最短、费用最省。*/

状压dp

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>

#define DEBUG printf("Passing [%s] in Line %d\n" , __FUNCTION__ , __LINE__) ;

const int Percent[4] = {6 , 4 , 45 , 55} ;
const int MAX_N = 20 + 5 , MAX_S = (1 << 20) + 10 , ADAY = 12 * 60 , INF = 0x3f3f3f3f ;

struct DATA {
	int val , dis , times ;
	friend bool operator <(DATA a , DATA b) {return a.val < b.val ;}
	friend bool operator >(DATA a , DATA b) {return a.val > b.val ;}
	friend DATA operator +(DATA a , DATA b) {return (DATA){a.val + b.val , a.dis + b.dis , a.times + b.times} ;}
}dis[MAX_N][MAX_N] , f[MAX_S][MAX_N] ;

//vertex 0 means home
int T , n , m , cost[MAX_N] , g[MAX_S][MAX_N] ;

void init() {
	for (int i = 0 ; i <= n ; ++i)
		for (int j = 0 ; j <= n ; ++j) dis[i][j] = (DATA){INF , 0 , 0} ;
}

void print(int S , int a) {
	if (!S) return ;
	print(S - (1 << a) , g[S][a]) ;
	printf("%d " , a) ;
}

int main() {
	scanf("%d" , &T) ;
	for (; T-- ;) {
		scanf("%d %d" , &n , &m) ;
		for (int i = 1 ; i <= n ; ++i) scanf("%d" , &cost[i]) ;
		init() ;

		//Calculate the best path
		for (int i = 0 ; i < m ; ++i) {
			int x , y , w ;
			scanf("%d %d %d" , &x , &y , &w) ;

			dis[x][y] = dis[y][x] = (DATA){w * Percent[0] + 1 * Percent[1] , w , 1} ;
		}
		for (int k = 0 ; k <= n ; ++k)
			for (int i = 0 ; i <= n ; ++i)
				for (int j = 0 ; j <= n ; ++j)
					dis[i][j] = std::min(dis[i][k] + dis[k][j] , dis[i][j]) ;

		//Calculate all of the solutions in the best situation
		int siz = 1 << (n + 1) ;
		for (int S = 0 ; S < 2 ; ++S)
			for (int i = 0 ; i <= n ; ++i) f[S][i] = (DATA){-1 , 0 , 0} ;

		f[1][0] = (DATA){0 , 0 , 0} ;
		for (int S = 2 ; S < siz ; ++S)
			for (int i = 0 ; i <= n ; ++i) {
				f[S][i] = (DATA){-1 , 0 , 0} ;
				if (!(S & (1 << i))) continue ;

				int preS = S - (1 << i) ;
				if (!preS) continue ;

				for (int j = 0 ; j <= n ; ++j) {
					if (f[preS][j].val == -1) continue ;

					DATA tmp = f[preS][j] + dis[i][j] ;
					if (f[S][i] > tmp || f[S][i].val == -1) f[S][i] = tmp , g[S][i] = j ;
				}
			}

		//Assess
		int BSet = 0 , BEnd = -1 , BValue = -1 ;
		for (int S = 2 ; S < siz ; ++S) {
			int BestEnd = -1 ; DATA BestVal = (DATA){-1 , 0 , 0} ;
			for (int i = 0 ; i <= n ; ++i) {
				if (f[S][i].val == -1) continue ;

				DATA tmp = f[S][i] + dis[i][0] ;
				if (tmp.dis <= ADAY && (BestVal > tmp || BestVal.val == -1)) BestVal = tmp , BestEnd = i ;
			}

			if (BestVal.val == -1) continue ;

			int TotalCost = 0 , Count = 0 ;
			for (int i = 1 ; i <= n ; ++i) if (S & (1 << i)) TotalCost += cost[i] , ++Count ;

			int AssessVal = (TotalCost * Percent[2] + BestVal.val * Percent[3]) / Count ;
			if (BValue > AssessVal || BValue == -1) BSet = S , BEnd = BestEnd ;
		}

		print(BSet , BEnd) ; printf("0\n") ;
	}

	return 0 ;
}

/*

部分变量解释

w[i]的单位为分钟
ADAY 表示一天可游玩的时间,依据w[i]的单位而定,此处为 12 * 60

可以根据需求而修改
其余变量下面会进行详细解释

---

输入格式
T
n m
cost[0 ... n]
u[0...m] v[0...m] w[0...m]

其中
T为数据组数
n为地点 m为交通路线
cost[i]为参观每个地点所需的费用
u[i] v[i] w[i]表示存在一条连接u[i]与v[i]且消耗时间为w[i]的交通路线(双向)

---

思路

**第一部分**
对每条路径而言,存在 w 的时间消耗 以及 1 的中转消耗
对路径进行加权平均计算价值

价值计算方法为
(w * Percent[0] + 1 * Percent[1]) / (Percent[0] + Percent[1])
为了简便运算  在编写过程中将分母除去,仅以分子作为价值

对加权平均后的图跑 Floyd 可得到任意两点 时间和中转最优的方案
如果需要保存路径 可在 Floyd 处添加记录

**第二部分**
设 f[S][i] 表示观光 S 集合内的地点且最后访问的地点为i的最优方案
利用dp可轻易计算出方案,其中 g[S][i] 用于保存dp是从哪个状态转移过来的,便于输出方案

**第三部分**
对于每个观光方案 S ,都找出最优的最后访问节点 BestEnd
即先确定当前方案 S 的最优走法 BestVal ,且保证这个走法的时间是严格限制在ADAY范围内的
(保证一日游的需求,如果需要添加地点游玩时间,可在ADAY的判断条件处稍作修改)

接着对于该方案S,消耗的费用为 TotalCost , 去过的地点为 Count
题目要求 时间、中转、费用最优,以及能够参与更多的景点,同样考虑构造一个函数进行最优化处理

其函数的设计为
(TotalCost * Percent[2] + BestVal.val * Percent[3]) / Count
从而找到最优的方案

*/
posted @ 2021-01-02 15:03  TCPP  阅读(267)  评论(13编辑  收藏  举报