【算法竞赛入门经典例题题解】 【DP】
UVA1025 练习城市里的间谍 A Spy in the Metro
洛谷链接 UVA1025 练习城市里的间谍 A Spy in the Metro
题目
某城市地铁是一条直线,有 nn(2\leq n\leq 502≤n≤50)个车站,从左到右编号 …n。有 M1辆列车从第 1 站开始往右开,还有 M2 辆列车从第 n 站开始往左开。列车在相邻站台间所需的运行时间是固定的,因为所有列车的运行速度是相同的。在时刻 0,Mario 从第 1 站出发,目的在时刻 T( 2000≤T≤200)会见车站 n 的一个间谍。在车站等车时容易被抓,所以她决定尽量躲在开动的火车上,让在车站等待的时间尽量短。列车靠站停车时间忽略不计,且 Mario 身手敏捷,即时两辆方向不同的列车在同一时间靠站,Mario 也能完成换乘。
大意
就是你从车站1出发在时刻T到达车站n,要求你尽可能在车上,输出最小在车站等待时间
思路
- 首先要思考影响决策的因素,这道题中变量是时间T和车站n,因此我们可以定义dp[ i ] [ j ] 在 i 时刻中你在 j 车站 最小等待时间
- 接下来思考dp[ i ] [ j ] 这个集合的划分 ,也就是dp[ i ] [ j ] 是如何转化过来的 , 那么根据题意,如果说到达j车站,一,我在上一个时刻就在这个车站等待,二,我之前乘坐向右的车在 i 时刻到达 j ,三 我乘坐向左的车在 i 时刻到达 j ,那么我们就可以写出状态转移方程
dp[ i ] [ j ] = min(dp[ i -1] [ j ] ,dp[i + t[j-1]],dp[ i + t[j]] [ j +1 ] )
AC代码
#include<bits/stdc++.h>
const int oo=0x3f3f3f3f;
using namespace std;
int n,T,M1,M2,kase;
int t[55];
bool trainl[55][10010];//train[i][j]:第i站第j时间是否有train
bool trainr[55][10010];
int dp[10010][55];//dp[i][j]:在时刻i你在车站j最低等多少时间
int main(){
while(cin>>n && n!=0){
kase++;
cin>>T;
memset(t, 0, sizeof(t));
memset(trainl, 0, sizeof(trainl));
memset(trainr, 0, sizeof(trainr));
for(int i=1; i<=n-1; i++) cin>>t[i];//t[i]:i~i+1站时间
cin>>M1;
for(int i=1; i<=M1; i++){
int t1;//左站出发时间
cin>>t1;
int sum=t1;
for(int j=1; j<=n; j++){
trainl[j][sum] = 1;
sum+=t[j];
}
}
cin>>M2;
for(int i=1; i<=M2; i++){
int t2;//右站出发时间
cin>>t2;
int sum=t2;
for(int j=n; j>=1; j--){
trainr[j][sum] = 1;
sum+=t[j-1];
}
}
for(int i=1; i<=n-1; i++)
dp[T][i]=0x3f3f3f3f;
dp[T][n] = 0;
for(int i=T-1; i>=0; i--)
for(int j=1; j<=n; j++){
dp[i][j]=dp[i+1][j]+1;
if(j<n && trainl[j][i] && i+t[j]<=T )
dp[i][j] = min(dp[i][j], dp[i+t[j]][j+1]);
if(j>1 && trainr[j][i] && i+t[j-1]<=T )
dp[i][j] = min(dp[i][j], dp[i+t[j-1]][j-1]);
}
cout<< "Case Number " << kase<< ": ";
if(dp[0][1] >= oo) cout<< "impossible\n";
else cout<<dp[0][1] <<"\n";
}
return 0;
}
UVA437 巴比伦塔 The Tower of Babylon
题意
定义一种塔,只有一个方块的底的两条边严格小于另一个方块的底的两条边,这个方块才能堆在另一个上面。方块可以任意旋转。给出n个方块包含三个尺寸(a , b , c) ,问最高的塔高度是多少
思路(DAG上动态规划)
假设我们先不管旋转的问题,考虑每个方块是否能叠起来的问题,那么只有宽严格大于并且长严格大于另一个合法,我们把这种关系变成图上的单项边,把高作为边上的权值,在寻找最高塔的过程中就是找最长路 ,设dp[ i ]为以i为起点的最长路
那么 dp[ i ] = max(dp[j]+1) ( i与j有边)
再考虑旋转的问题,其实就是把一个立方体变成三个立方体,长宽高轮转一下
还有一些细节: 如何建边 1 . 根据上述的关系 构造出一个图来 2 . 一边dp一边暴力判断是否有边(我是这么写的,代码量少)
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+7;
typedef long long ll;
struct cube{
int x,y,z;
bool operator > (const cube a)const {
return (x>a.x&&y>a.y)||(x>a.y&&y>a.x);
}// 判断边
}A[1000];
int cnt_cube = 0;
int dp[1000];
int dfs(int ix){ //立方体下标
if(dp[ix]>0) return dp[ix];
dp[ix] = A[ix].z;
for(int i = 0;i<cnt_cube;i++){
if(A[ix]>A[i]){
dp[ix] = max(dp[ix],dfs(i)+A[ix].z);
}
}
return dp[ix];
}
int main(){
int n;
int cass = 1;
while(cin>>n,n){
cnt_cube = 0;
for(int i = 0;i<n;i++){
int a,b,c;
cin>>a>>b>>c;
A[cnt_cube++] = {a,b,c};
A[cnt_cube++] = {a,c,b};
A[cnt_cube++] = {b,c,a};
}
int res = -1;
memset(dp,0,sizeof(dp));
for(int i = 0;i<cnt_cube;i++){ // 以i为起点的最大高度
res = max(res,dfs(i));
}
printf("Case %d: maximum height = %d\n",cass++,res);
}
return 0;
}