Roads
N个城市,编号1到N。城市间有R条单向道路。 每条道路连接两个城市,有长度和过路费两个属性。 Bob只有K块钱,他想从城市1走到城市N。问最短共需要走多长的路。如果到不了N ,输出-1
2<=N<=100
0<=K<=10000
1<=R<=10000
每条路的长度L, 1 <= L <= 100
每条路的过路费T , 0 <= T <= 100、
输入:
K
N
R
s1 e1 L1 T1
s1 e2 L2 T2
...
sR eR LR TR
s e是路起点和终点
解题思路
从城市1开始深度优先遍历整个图,找到所有能过到达N 的走法, 选一个最优的。\
最优性剪枝:
- 如果当前已经找到的最优路径长度为L ,那么在继续搜索的过程中,总长度已经大 于等于L的走法,就可以直接放弃,不用走到底了.
保存中间计算结果用于最优性剪枝:
如果到达某个状态A时,发现前面曾经也到达过A,且前面那次到达A所花代价更 少,则剪枝。这要求保存到达状态A的到目前为止的最少代价。
- 用midL[k][m] 表示:走到城市k时总过路费为m的条件下,最优路径的长度。若在 后续的搜索中,再次走到k时,如果总路费恰好为m,且此时的路径长度已经不小于 midL[k][m],则不必再走下去了。
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
int K,N,R;
struct Road {
int d,L,t;
};
vector<vector<Road> > cityMap(110); //邻接表。cityMap[i]是从点i有路连到的城市集合
int minLen = 1 << 30; //当前找到的最优路径的长度
int totalLen; //正在走的路径的长度
int totalCost ; //正在走的路径的花销
int visited[110]; //城市是否已经走过的标记
int minL[110][10100]; //minL[i][j]表示从1到i点的,花销为j的最短路的长度.
void Dfs(int s) {
//如果当前点为终点,则更新最优路径长度
if (s==N) {
minLen=min(minLen,totalLen);
return;
}
//遍历当前点的所有邻接点
for( int i = 0 ;i < cityMap[s].size(); ++i ) {
int d = cityMap[s][i].d; //s 有路连到d
//如果邻接点未被访问过
if(! visited[d] ) {
int cost = totalCost + cityMap[s][i].t;
//如果当前路径的花销超过K,则跳过
if( cost > K)
continue;
//如果当前路径的长度加上邻接点的长度大于等于最优路径长度,则跳过
if( totalLen + cityMap[s][i].L >= minLen ||
totalLen + cityMap[s][i].L >= minL[d][cost])
continue;
//更新当前路径的长度和花销
totalLen += cityMap[s][i].L;
totalCost += cityMap[s][i].t;
//更新邻接点的最短路径长度
minL[d][cost] = totalLen;
//标记邻接点为已访问
visited[d] = 1;
//递归访问邻接点
Dfs(d);
//回溯,取消邻接点的访问标记
visited[d] = 0;
//回溯,更新当前路径的长度和花销
totalCost -= cityMap[s][i].t;
totalLen -= cityMap[s][i].L;
}
}
}
int main()
{
//输入K,N,R
cin>>K>>N>>R;
//输入R条道路信息
for (int i = 0; i < R; ++i) {
int s;
Road r;
cin>>s>>r.d>>r.L>>r.t; //s是起点,r.d是终点,r.L是长度,r.t是花费
if (s!=r.d) //如果起点和终点不是同一个点,就加入邻接表
cityMap[s].push_back(r);
}
for( int i = 0;i < 110; ++i )
for( int j = 0; j < 10100; ++ j )
minL[i][j] = 1 << 30;
memset(visited,0,sizeof(visited));
totalLen=0;
minLen=1<<30;
totalCost=0;
visited[1]=1;
Dfs(1);
if( minLen < (1 << 30))
cout << minLen << endl;
else
cout << "-1" << endl;
}
输入
5
6
7
1 2 2 3
2 4 3 3
3 4 2 4
1 3 4 1
4 6 2 1
3 5 2 0
5 4 3 2输出
11
生日蛋糕
要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体。 设从下往上数第i(1 Ri+1且Hi > Hi+1。
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的 下底面除外)的面积Q最小。
令Q = Sπ
请编程对给出的N和M,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小 。
- 深度优先搜索,枚举什么? 枚举每一层可能的高度和半径。
- 如何确定搜索范围? 底层蛋糕的最大可能半径和最大可能高度
- 搜索顺序,哪些地方体现搜索顺序? 从底层往上搭蛋糕,而不是从顶层往下搭 中国大学
- 如何剪枝?
剪枝1:搭建过程中发现已建好的面积已经不小于目前求得的最优表 面积,或者预见到搭完后面积一定会不小于目前最优表面积,则停止搭建 (最优性剪枝)
剪枝2:搭建过程中预见到再往上搭,高度已经无法安排,或者半径 已经无法安排,则停止搭建(可行性剪枝)
剪枝3:搭建过程中发现还没搭的那些层的体积,一定会超过还缺的 体积,则停止搭建(可行性剪枝)
剪枝4:搭建过程中发现还没搭的那些层的体积,最大也到不了还缺 的体积,则停止搭建(可行性剪枝
#include <iostream>
#include <cmath>
using namespace std;
int N,M;
int minArea = (1 << 30);
int area = 0;
//用n层去凑体积v,允许的最大高度为h,最大半径为r
void dfs(int v,int n,int r,int h){
if(n == 0){
if(v) //层数为0体积不为0,凑不出来了
return;
else{ //体积为0,凑完了
minArea = min(area,minArea);
return;
}
}
if(v <= 0)
return;
//遍历每一个可能的高度对应可能的半径
for(int rr = r;rr>=n;rr--){ //用n层去凑,半径最小得是n,比n再小,n以上的层数没法放了
//可行性剪枝,如果剩下的半径不足以分配给剩下的层数,停止
if((rr-1) < (n-1))
continue;
//如果现在正好凑的是第M层(底面),area初始化为rr的平方
//因为所有层蛋糕上表面的面积都投影到底面上,正好拼成一个底面圆,之后处理的时候只需要考虑每一层侧面积而无需考虑上表面积了
if(n == M)
area = rr * rr;
for(int hh = h;hh>=n;hh--){
//可行性剪枝,如果剩下的高度不足以分配给剩下的层数,停止
if((hh-1) < (n-1))
continue;
//最优化剪枝,如果当前面积大于已经求得的最优值,或者预见到以后的面积会大于已经求得的最优值,停止
if((area + 2 * rr * hh) >= minArea || (area + 2 * rr * hh + (n-1)*n*(2*n-1)/3) > minArea)
continue;
//可行性剪枝,如果还没搭的那些层的体积一定会大于还缺少的体积,停止
if(((n-1)*n/2)*((n-1)*n/2) > (v - rr*rr*hh))
continue;
//可行性剪枝,如果还没搭的那些体积最大也无法填补还缺的体积,停止
long long leftV = 0;
for(long long t = 1;t<=(n-1);t++){
leftV += (rr-t)*(rr-t)*(hh-t);
}
if(leftV < (long long)(v - rr*rr*hh))
continue;
area += 2 * rr * hh;
dfs(v - rr*rr*hh,n-1,rr-1,hh-1);
//递归返回的时候要再减去侧面积值,因为下一次for循环讨论的是另外一种可能的高度值,我们是要找一个最佳方案,所以必须把上一次的影响清除
area -= 2 * rr * hh;
}
}
}
int main(){
cin >> N >> M;
//最大半径
int maxR = sqrt(N);
int maxH = N;
dfs(N,M,maxR,maxH);
if(minArea == (1 << 30))
cout << 0 << endl;
else
cout << minArea << endl;
return 0;
}
输入100 2
输出68