恩,网上个个哥们都说这是个水题,惭愧啊,我太水了,只能向大神们学习了。
转自博文:http://www.cnblogs.com/yangliu/archive/2011/12/22/2298497.html
http://blog.csdn.net/x_liushi_game/article/details/7538632
题目大意:从A到B有若干个车站,编号从0到m,列车的最大载客量是n。每次列车开车之前,会从各个车站收集订票信息。一共有t条订票信息,一条订票信息包括:起点站,终点站,人数。票价在数值上等于起点站与终点之间的车站数(包括终点站,不包括起点站)。由于列车的最大载客量是一定的,所以不一定能接受所有的订票。对于一条订票order,只能全部接受,或者是全部拒绝。现在选择接受订票使之利润最大,输出这个最大利润。
注:题目出自http://poj.org/problem?id=1040
分析:这是一个DFS问题,但如果不剪枝的话就很容易TLE。用数组stop[i]表示列车在车站i时车上的总人数。按照订票order的顺序DFS:分别以第1个到第t个order开始,每接受一个order,就把沿线的stop全部加上这个order中的人数,如果发现哪个stop的人数超过了最大载客量n,就必须停下来,这个order是不能接受的,就把stop中加上的人数全部减下来。如果所有沿线的stop都没有超过n,就可以加上该order的利润,开始测试下一个order,测试完成之后取消沿线stop加的人数回溯。每当开始测试order时,就把已经能获得的利润用参数的形式传递过去,保存这个最大值maxe。最后把所有的情况都遍历之后,输出maxe即得结果。
代码:
#include <iostream> #include <cstdlib> #include <algorithm> using namespace std; #define maxm 23 //订单最大23 #define maxn 8 //最大的站为8 int cap, n, m; //cap表示容量,n表示站数,m表示订单数 int ans; //最后结果 int ocount; int down[maxn]; //表示在i站下车的人数 struct Order{ int s, e, p; }order[maxm]; int max(int a,int b){ return a>b?a:b; } bool operator < (const Order &a, const Order &b){ if (a.s == b.s) return a.e < b.e; return a.s < b.s; } void dfs(int i, int p, int money){ if (i == m){ ans = max(ans, money); return; } if (i > 0)//前一个订单下车的减掉 for (int j = order[i - 1].s + 1; j <= order[i].s; j++) p -= down[j]; if (p + order[i].p <= cap) {//如果加上第i个订单后没有超过火车的容量就可以接受i个订单 down[order[i].e] += order[i].p; //down[i]表示i号车站有多少人下车 dfs(i + 1, p + order[i].p, money + order[i].p * (order[i].e - order[i].s)); down[order[i].e] -= order[i].p; //恢复现场,以便后面的回溯 } dfs(i + 1, p, money); //没有接受第i个订单 } int main(){ int i; while(cin>>cap>>n>>m,cap||n||m){ for(i=0;i<m;i++){ scanf("%d%d%d", &order[i].s, &order[i].e, &order[i].p); } sort(order,order+m); ans = 0; dfs(0,0,0); printf("%d\n",ans); } return 0; }
作者对DFS算法实现深度解析:
1、 对order数组排序,使得票的始发站升序排练,当始发站相同时,使得终点站升序排列,这样便于统计每张票的始发站处下车的人数。
2、 DFS递归函数返回有两种情况:其一是i==m时该条路径搜索结束返回结果到原始调用处;其二是执行到DFS末尾返回,也是返回到调用该次DFS函数处。注意区分34行与37行调用DFS函数的不同,开始选择第1、2、3票搜索路径的DFS函数调用情况如下图所示
第34行的DFS调用是考虑选择第i张票,继续深度搜索;第37行的DFS调用是考虑不选择第i张票(可能由于选第i张票超过了火车容量限制或者是前面的搜索选择完毕回溯)调用的地方不同,自然dfs函数返回的地方也不同。
通过本题的分析,总结经验如下:
算法学习最忌讳粗枝大叶,很多看似思想简单的算法实现成代码运行就有很多新的难题,比如深度搜索DFS的实现,有以下关键点:
1、由于搜索过程高度重复化,一般写成递归函数,DFS函数里面写明返回条件,不同情况下不同的递归调用条件。
2、 DFS函数的构造一般有一个变量记录当前搜索的位置(如本题中i记录了当前搜索到了第几张票的选择),另外若干变量保存需要一直维护状态变量(如本题中的p记录了当前火车上的人数,money记录了当前选择方案下的总钱数)
3、 DFS发生回溯的时候,要注意恢复原来状态变量的值(又叫“恢复现场”),比如第35行 down[order[i].e] -=order[i].p; 这里是由于前面考虑选择第i张票的搜索路径已经搜索完毕,那么下面考虑不选择第i张票,恢复下车人数数组down的状态到不选第i张票的情况,在第37行直接去考察第i+1张票的选择情况。
最后,对于调用及执行情况比较复杂的代码,用好VC等IDE单步调试,观察callback、watch等窗口中函数调用、关键变量值的变化是很重要的研究途径,调试工具要充分利用。有必要学习一下重要的调试技巧,对于加深对程序的理解有好处。
另外关于CSDN上同样的思想,附上代码:
#include <stdio.h> #include <string.h> int orders[27][4]; int maxPeoples, endStation, numOrders;; int max; //保存最终结果 int leane[8]; //车上的人 void train(int n, int sum) { int i; if (sum > max) max = sum; if(n >= numOrders) return; for (i = orders[n][0]; i < orders[n][1]; i++) if (leane[i]+orders[n][2] > maxPeoples) //依次扫描第i个订单所经过的站+加上i个订单要上的人 break; //如果超过车的容量则退出 if (i >= orders[n][1]) { //加上第i个订单的人,并没有超过车的容量,则接受第i个订单 for (i = orders[n][0]; i < orders[n][1]; i++) leane[i] += orders[n][2]; //将第i个订单上的人更新到leane[],以便继续DFS train(n+1, sum+orders[n][3]); //继续DFS for (i = orders[n][0]; i < orders[n][1]; i++) leane[i] -= orders[n][2]; //恢复到没有接收第i个订单的现成,以便回溯 } train(n+1, sum); //没有接受第i个订单 } int main() { int i; int a, b, p; while (scanf("%d%d%d", &maxPeoples, &endStation, &numOrders), maxPeoples||endStation||numOrders) { memset(leane, 0, sizeof(leane)); for (i = 0; i < numOrders; i++) { scanf("%d%d%d", &a, &b, &p); orders[i][0] = a; orders[i][1] = b; orders[i][2] = p; orders[i][3] = (b-a)*p; } max = 0; train(0, 0); printf("%d\n", max); } return 0; }
后记:继续加油吧,骚年!