DFS+剪枝 寻路问题 生日蛋糕

寻路问题

Description

N cities named with numbers 1 ... N are connected with one-way roads. Each road has two parameters associated with it : the road length and the toll that needs to be paid for the road (expressed in the number of coins).
Bob and Alice used to live in the city 1. After noticing that Alice was cheating in the card game they liked to play, Bob broke up with her and decided to move away - to the city N. He wants to get there as quickly as possible, but he is short on cash.

We want to help Bob to find the shortest path from the city 1 to the city N that he can afford with the amount of money he has.

Input

The first line of the input contains the integer K, 0 <= K <= 10000, maximum number of coins that Bob can spend on his way.
The second line contains the integer N, 2 <= N <= 100, the total number of cities.

The third line contains the integer R, 1 <= R <= 10000, the total number of roads.

Each of the following R lines describes one road by specifying integers S, D, L and T separated by single blank characters :

  • S is the source city, 1 <= S <= N
  • D is the destination city, 1 <= D <= N
  • L is the road length, 1 <= L <= 100
  • T is the toll (expressed in the number of coins), 0 <= T <=100


Notice that different roads may have the same source and destination cities.

Output

The first and the only line of the output should contain the total length of the shortest path from the city 1 to the city N whose total toll is less than or equal K coins.
If such path does not exist, only number -1 should be written to the output.

Sample Input

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

Sample Output

11

 超时代码:

#include<iostream>
using namespace std;
#include<vector>//邻接表
//从城市1出发开始深搜图,找到所有能到达N的走法中最优的
int K, N, R;
struct Road {
	int d, L, t;
};
vector<vector<Road>> G(110);//G[i]:从节点i出发的所有边
int minLen;
int totalLen;
int totalCost;
int visited[110];
void Dfs(int s) {//从s点出发,进行深搜
	if (s == N) {
		minLen = min(minLen, totalLen);
		return;
	}
	//遍历s点相连的新点
	for (int i = 0; i < G[s].size(); i++) {
		Road r= G[s][i];
		if (totalCost + r.t > K)
			continue;
		if (!visited[r.d]) {
			totalLen += r.L;
			totalCost += r.t;
			visited[r.d] = 1;
			Dfs(r.d);
			visited[r.d] = 0;
			totalLen -= r.L;
			totalCost -= r.t;
		}
	}
}
int main() {
	cin >> K >> N >> R;
	for (int i = 0; i < R; i++) {
		int s;
		Road r;
		cin >> s >> r.d >> r.L >> r.t;
		if (s != r.d) {
			G[s].push_back(r);
		}
	}
	//初始化
	memset(visited, 0, sizeof(visited));
	totalLen = 0;
	minLen = 1 << 30;
	totalCost = 0;
	visited[1] = 1;
	Dfs(1);
	if (minLen < (1 << 30)) {
		cout << minLen;//找到了
	}
	else
		cout << -1;//找不到
	return 0;
}

剪枝

剪枝
1.可行性剪枝

当前条件不满足再走下去的条件,到不了最后的目的地,如路费不够
2.最优性剪枝

就算可以走到最后的目的地,但是不是最优的
1)当前已经找到了最优路径长度是L,那么在继续搜索过程中,总长度大于等于L的走法,可以直接放弃
2)走到点k的路径最小长度,minL[k],走到点k的最短路径长度;加上钱的因素
minL[k][m],走到城市k时,且总路费是m,,最优路径的长度
后续搜索中,再次走到k且花费是m时,此时路径大于等于minL[k][m],不必再走了

//DFS,剪枝
#include<iostream>
using namespace std;
#include<vector>//邻接表
//从城市1出发开始深搜图,找到所有能到达N的走法中最优的
int K, N, R;
struct Road {
	int d, L, t;
};
vector<vector<Road>> G(110);//G[i]:从节点i出发的所有边
int minLen;//最优路径长度
int totalLen;//当前探索的路径
int totalCost;//总花费
int visited[110];//标记
//minL[i][j]的意义 路费花了j,走到点i的最优路径长度
int minL[110][10010];//空间换时间
void Dfs(int s) {//从s点出发,进行深搜
	if (s == N) {
		minLen = min(minLen, totalLen);
		return;
	}
	//遍历s点相连的新点
	for (int i = 0; i < G[s].size(); i++) {
		Road r= G[s][i];
		if (totalCost + r.t > K)
			continue;
		if (!visited[r.d]) {//加判断
			if (totalLen + r.L >= minLen)
				continue;
			//剪枝 - -记录 -记录中间结果的可行性剪枝
			if (totalLen + r.L >= minL[r.d][totalCost + r.t])
				continue;
			minL[r.d][totalCost + r.t] = totalLen + r.L;//记录
			totalLen += r.L;
			totalCost += r.t;
			visited[r.d] = 1;
			Dfs(r.d);
			visited[r.d] = 0;
			totalLen -= r.L;
			totalCost -= r.t;
		}
	}
}
int main() {
	cin >> K >> N >> R;
	for (int i = 0; i < R; i++) {
		int s;
		Road r;
		cin >> s >> r.d >> r.L >> r.t;
		if (s != r.d) {
			G[s].push_back(r);
		}
	}
	//初始化
	memset(visited, 0, sizeof(visited));
	totalLen = 0;
	minLen = 1 << 30;
	totalCost = 0;
	visited[1] = 1;
	for (int i = 0; i < 110; i++)
		for (int j = 0; j < 10010; j++)
			minL[i][j] = 1 << 30;
	Dfs(1);
	if (minLen < (1 << 30)) {
		cout << minLen;//找到了
	}
	else
		cout << -1;//找不到
	return 0;
}

 生日蛋糕

题目:7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体。

设从下往上数第i层蛋糕是半径为Ri, 高度为Hi的圆柱。

当i < M时,要求Ri > Ri+1且Hi > Hi+1。

由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。

令Q = Sπ ,请编程对给出的N和M,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小。

除Q外,以上所有数据皆为正整数 。

输入格式

输入包含两行,第一行为整数N(N <= 10000),表示待制作的蛋糕的体积为Nπ。

第二行为整数M(M <= 20),表示蛋糕的层数为M。

输出格式

输出仅一行,是一个正整数S(若无解则S = 0)。

数据范围

1≤N≤10000,
1≤M≤20

输入样例:

100
2

输出样例:

68

 状态 -状态看作点 -一个操作使一个状态转移到另一个状态

DFS,也是枚举:枚举每一层可能的高度和半径

确定搜索范围:底层蛋糕的最大可能半径和最大可能高度


搜索顺序-从下往上搭还是从上往下搭:从下往上搭,先处理大的


剪枝:可行性剪枝,最优性剪枝

先来了解一下本题的状态:Dfs(int v,int n,int r,int h),意义是,搭建一个n层的蛋糕,使其体积是v,蛋糕最底层的最大可能半径是r,蛋糕的最底层的最大可能高度是h,求出搭建好的这个蛋糕,将其最小表面积放到minArea中。 

边界条件:n是0,更新minArea,返回;v是0,返回。

枚举所有和该状态相关的状态,下个状态的特征是蛋糕层数变为n-1层,最大可能半径变为r-1,最大可能高度变为h-1,要凑的体积变为??。这个要凑的体积需要我们想一下。如果每轮递归可以解决当前要处理的n层蛋糕的最下面一层蛋糕的半径和高度的话,我们上面的??就是n层蛋糕要凑的体积减去已经确定的最下面一层蛋糕用去的体积。对于最下面蛋糕可能用去的体积,我们回到状态的定义来看,最下面一层蛋糕可能用去的体积是,或者说最下面一层蛋糕的半径和高度可能是,半径小于等于底层最大可能半径r,高度小于等于最大可能高度h——这是显然的。据此可以枚举所有可能的底层蛋糕半径和高度,就是处理了最下面的一层蛋糕,下面要处理的就是n-1层蛋糕的搭建,得到递归。事实上,最下面一层蛋糕的可能半径和可能高度,可能半径小于等于r,并且,可能半径大于等于n,为什么,试想,要堆m层,最上面那层半径最小是1,下面层的半径比上面层的大,那么从上到下,蛋糕半径下面的至少是上面的加1,所以,最顶层半径最小是1时,最底层半径最少是n。即底层的蛋糕半径最小是n。同理,最底层的可能高度小于等于h,并且大于等于n。枚举底层蛋糕的半径和高度分别从n到r、从n到h枚举。

可行性剪枝:当前的状态搭不到凑成体积是v的n层蛋糕。

状态Dfs(int v,int n,int r,int h)搭不到体积是v的n层蛋糕,看一下该状态的意义,搭建一个n层的蛋糕,使其体积是v,蛋糕最底层的最大可能半径是r,蛋糕的最大可能高度是h

得出以下三种情况

1):最大体积也凑不成v,在这个r和h的限制下,搭n层的最大蛋糕的体积也比要凑的v小

最大体积是,底层的半径取r,底层的高度取h,向上每层半径和高度减1,直到搭了n层,对应得体积就是当前状态到达目的地需要的最大体积

2):最小体积也凑不成v,在这个r和h的限制下,搭n层蛋糕最小的体积也比要凑的v大

最小体积是,最上面那层蛋糕的高度和半径取1,往下依次半径和高度加1,最底层的高度和半径都是n,试想,最上面那层蛋糕要存在且半径和高度都是正整数,最小的正整数是1,此时算的体积就是在该状态下到达目的地需要的的最小体积

3):根本就搭不了n层蛋糕,因为n层蛋糕意味着底层蛋糕的最大可能半径和最大可能高度都大于等于n,当r和h都大于等于n,才能搭n层蛋糕,反之,则搭不了n层蛋糕

最优性剪枝:即使当前的状态能够搭到体积为v层数为n的蛋糕,即就算该状态到了目的地,也按照该状态搭的蛋糕的表面积不是最小的,即结果不是最优的   

到达当前状态的蛋糕的表面积或者预知将要递归的那个状态蛋糕的表面积会大于等于已经求得的最小表面积了,则即使可以到达目的地,搭好蛋糕,也不是最优解,直接剪枝减去。

根据体积N和层数M,怎么求一开始的底层最大可能半径和最大可能高度,我是粗略估计的,要搭M层蛋糕,求最大高度,那么半径就要最小,最小半径是,底层半径取M,顶层取1,设底层最大高度是maxH,那么顶层就是maxH-(M-1),顶层的半径和高度都最小,顶层的体积是1^2*(maxH-(m-1)),那么要凑的体积N>=M*1^2*(maxH-(M-1)), 求出即可;求最大半径,同理,设底层最大半径是maxR,当顶层的高度是1,顶层的半径是maxR-(M-1),顶层的体积是(maxR-(M-1))^2*1,那么要凑的体积N>=M*(maxR-(M-1))^2*1,求出即可。

上面只是粗略求出maxH和maxR.


//枚举每一层可能的高度和半径
//底层蛋糕的最大可能半径和最大可能高度
//搜索顺序-从下往上搭还是从上往下搭- - 从下往上搭,先处理大的
//剪枝
//
#include <iostream>
#include <vector>
#include <cstring>
#include <cmath>
using namespace std;
int N, M;//
int minArea = 1 << 30;//最优表面积
int area = 0;//正在搭建中的蛋糕的表面积
//状态
void Dfs(int v, int n, int r, int h) {
	//搭一个n层的蛋糕去凑体积V,最底层半径不能超过r,高度不能超过h
	//求出最小表面积放入minArea
	if (n == 0) {
		if (v)return;
		else
		{
			minArea = min(minArea, area);
		}
	}
	if (v <= 0)
		return;
	if (r < n || h < n)//搭不了n层
		return;
	int tmpMaxV = 0, tmpMinV = 0;//能搭的最大的,能搭的最小的
	for (int i = 0; i < n; i++) {
		tmpMaxV += (r - i) * (r - i) * (h - i);
		tmpMinV += (n - i) * (n - i) * (n - i);
	}
	if (tmpMaxV < v || tmpMinV>v)//最大的到不了V,最小的也比V大
		return;
	
	for (int rr = r; rr >= n; rr--) {
		if (n == M)//底面积
			area = rr * rr;
		//剪枝
		for (int hh = h; hh >= n; hh--) {
			if (area + 2 * rr * hh >= minArea)//很有效
				continue;
			area += 2 * rr * hh;
			Dfs(v - rr * rr * hh, n - 1, rr - 1, hh - 1);
			area -= 2 * rr * hh;
		}
	}

}
int main() {
	cin >> N >> M;
	int maxR = 1, maxH = 1;
	//求出maxR,maxH
	while (true)
	{
		if (M * (maxR - M + 1) * (maxR - M + 1) > N)
			break;
		maxR++;
	}
	maxR--;
	while (true) {
		if (M * (maxH - M + 1) > N)
			break;
		maxH++;
	}
	maxH--;
	//cout << maxR << " " << maxH;
	Dfs(N, M, maxR, maxH);
	if (minArea == 1 << 30)
		cout << 0 << endl;
	else
		cout << minArea << endl;
	return 0;
}

 对于求解maxR和maxH的说明

bool isMaxH(int h) {
	int tmp = 0;
	for (int i = 0; i < M; i++) {
		tmp += (M - i) * (M - i) * h;
		if (tmp > N)//超过体积返回true- - 加等于号后,效果不大
			return true;
	}
	return false;
}
bool isMaxR(int r) {
	int tmp = 0;
	for (int i = 0; i < M; i++) {
		tmp += (M - i) * (r - i) * (r - i);
		if (tmp > N)//超过体积返回true- - 加等于号后,效果不大
			return true;
	}
	return false;
}
//经检验,上面两个函数求出的结果是错误的
//对于求这个maxR和maxH,不需要太精确,太精确的话可能会出现漏解错解
	//下面是我的测试 第一次出现的是
	/*
	while (true)
	{
		if (M * (maxR - M + 1) * (maxR - M + 1) > N)
			break;
		maxR++;
	}
	maxR--;
	while (true) {
		if (M * (maxH - M + 1) > N)
			break;
		maxH++;
	}
	maxH--;
	*/
	//代码求出的maxR和maxH
	//第二次出现的下面代码求出的
	/*
	while (true)
	{
		if (isMaxR(maxR))
			break;
		maxR++;
	}
	maxR--;
	while (true) {
		if (isMaxH(maxH))
			break;
		maxH++;
	}
	maxH--;*/
	/*N M maxR maxH 最小表面积
	* 100 2 8 51 68
	* 100 2 6 20 68
	* 10000 20 41 519 0
	* 10000 20 0 3 0
	* 10000 10 40 1009 1673 - -这个才是正确答案
	* 10000 10 16 25 1720--对于第二段代码,删去maxR--;和maxH--;语句即可
    *4999 8 31 631 1015
    *4999 8 13 24 1047--删去maxR--和maxH--也不行
    *还是采用第一种while循环范围较大点去求

	*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值