[SinGuLaRiTy] 动态规划题目复习
Posted SinGuLaRiTy2001
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[SinGuLaRiTy] 动态规划题目复习相关的知识,希望对你有一定的参考价值。
【SinGuLaRiTy-1026】 Copyright (c) SinGuLaRiTy 2017. All Rights Reserved.
[UVA 1025] A Spy in the Metro
题目描述
特工玛利亚被送到S市执行一个特别危险的任务。她需要利用地铁完成他的任务,S市的地铁只有一条线路运行,所以并不复杂。
玛利亚有一个任务,现在的时间为0,她要从第一个站出发,并在最后一站的间谍碰头。玛利亚知道有一个强大的组织正在追踪她,她知道如果一直呆在一个车站,她会有很大的被抓的风险,躲在运行的列车中是比较安全的。所以,她决定尽可能地呆在运行的列车中,她只能往前或往后坐车。
玛利亚为了能准时且安全的到达最后一个车站与对方碰头,需要知道在在车站最小等待时间总和的计划。你必须写一个程序,得到玛丽亚最短的等待时间。当然,到了终点站之后如果时间还没有到规定的时刻,她可以在车站里等着对方,只不过这个等待的时刻也是要算进去的。
这个城市有n个车站,编号是1-n,火车是这么移动的:从第一个车站开到最后一个车站。或者从最后一站发车然后开会来。火车在每特定两站之间行驶的时间是固定的,我们也可以忽略停车的时间,玛利亚的速度极快,所以他可以迅速上下车即使两辆车同时到站。
输入
输入文件包含多组数据,每组数据都由7行组成
第1行:一个正整数N(2<=N<=50)表示站的数量
第2行:一个正整数T(0<=T<=200)表示需要的碰头时间
第3行:1-(n-1)个正整数(0<ti<70)表示两站之间列车的通过时间
第4行:一个整数M1(1<=M1<=50)表示离开第一个车站的火车的数量
第5行:M1个正整数:d1,d2……dn,(0<=d<=250且di<di+1)表示每一列火车离开第一站的时间
第6行:一个正整数M2(1<=M2<=50)表示离开第N站的火车的数量
第7行:M2个正整数:e1,e2……eM2,(0<=e<=250且ei<ei+1)表示每一列火车离开第N站的时间
最后一行有一个整数0。
输出
对于每个测试案例,打印一行“Case Number N: ”(N从1开始)和一个整数表示总等待的最短时间或者一个单词“impossible”如果玛丽亚不可能做到。按照样例的输出格式。
样例数据
样例输入 | 样例输出 |
4 |
Case Number 1: 5 |
解析
一道DP题目,dp[i][j]表示到达第i个城市的时候 ,时间为j的等待时间最少是多少,然后转移方程即可。
Code
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<iostream>
#define MAXN 110
using namespace std;
int a[MAXN];
int d1[MAXN][MAXN],d2[MAXN][MAXN];
int m1,m2;
int dp[MAXN][400];
int main()
{
int n;
int T=1;
while(scanf("%d",&n)&&n!=0)
{
int t;
scanf("%d",&t);
for(int i=1;i<=n-1;i++)
scanf("%d",&a[i]);
scanf("%d",&m1);
for(int i=1;i<=m1;i++)
scanf("%d",&d1[1][i]);
sort(d1[1]+1,d1[1]+m1+1);
scanf("%d",&m2);
for(int i=1;i<=m2;i++)
scanf("%d",&d2[n][i]);
sort(d2[n]+1,d2[n]+m2+1);
dp[0][1]=0;
for(int i=1;i<=m1;i++)
for(int j=1;j<=n-1;j++)
d1[j+1][i]=d1[j][i]+a[j];
for(int i=1;i<=m2;i++)
for(int j=n-1;j>=1;j--)
d2[j][i]=d2[j+1][i]+a[j];
for(int i=0;i<=t;i++)
for(int j=1;j<=n;j++)
dp[j][i]=t+1;
dp[1][0]=0;
for(int j=0;j<=t;j++)
for(int i=1;i<=n;i++)
if(dp[i][j]<=t)
{
int k;
for(k=1;k<=m1;k++)
if(d1[i][k]>=j)
break;
if(d1[i][k]-j+dp[i][j]<dp[i+1][j+a[i]+d1[i][k]-j]&&k<=m1)
dp[i+1][j+a[i]+d1[i][k]-j]=d1[i][k]-j+dp[i][j];
for(k=1;k<=m2;k++)
if(d2[i][k]>=j)
break;
if(k<=m2&&d2[i][k]-j+dp[i][j]<dp[i-1][j+a[i-1]+d2[i][k]-j])
dp[i-1][j+a[i-1]+d2[i][k]-j]=d2[i][k]-j+dp[i][j];
}
for(int i=1;i<=t;i++)
if(dp[n][i]<t)
dp[n][t]=min(dp[n][i]+t-i,dp[n][t]);
if(dp[n][t]<=t)
printf("Case Number %d: %d\\n",T++,dp[n][t]);
else
printf("Case Number %d: impossible\\n",T++);
}
return 0;
}
[UVA 437] The Tower of Babylon
题目描述
或许你曾听过巴比伦塔的传说,现在这个故事的许多细节已经被遗忘了。现在,我们要告诉你整个故事:
巴比伦人有n种不同的积木,每种积木都是实心长方体,且数目都是无限的。第i种积木的长宽高分别为{xi,yi,zi}。积木可以被旋转,所以前面的长宽高是可以互换的。也就是其中2个组成底部的长方形,剩下的一个为高度。巴比伦人想要的用积木来尽可能地建更高的塔,但是两块积木要叠在一起是有条件的:只有积木A的底部2个边均小于积木B的底部相对的2个边时,这积木A才可以叠在积木B上方。例如:底部为3x8的积木可以放在底部为4x10的积木上,但是无法放在底部为6x7的积木上。
给你一些积木的数据,你的任务是写一个程式算出可以堆出的塔最高是多少。
输入
输入数据会包含多组数据。
在每一组数据中:第1行包含一个整数n,表示有n (1<=n<=30)种不同的积木。接下来的n行,每行给出3个整数,表示一块积木的长宽高。
当n=0时,输入数据结束。
输出
对于每一组数据,按照以下格式输出答案:
Case case: maximum height = height
样例数据
样例输入 | 样例输出 |
1 |
Case 1: maximum height = 40 |
解析
乍一看,有点像最长上升子序列类型的题目。对于积木可以翻转这一个条件,我们可以把不同状态(总共有6种,自己画图吧)下的积木看成不同种类的积木。为了方便以后DP的判断,我们在DP之前先小小地预处理以下:对每一种积木,按照底面积由小到大排序。后面的DP过程比较好想,大家可以看代码。
Code
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#define MAXN 30*6+10
using namespace std;
struct Block
{
int x,y,h;
void fun(int a,int b,int c)
{
x=a;
y=b;
h=c;
}
}node[MAXN];
bool cmp(Block r,Block t)
{
return r.x*r.y<t.x*t.y;
}
int dp[MAXN];
int main()
{
int num,cnt=0;
while(scanf("%d",&num)!=EOF)
{
if(!num)
return 0;
int a,b,c;
int m=0;
for(int i=0;i<num;i++)
{
cin>>a>>b>>c;
node[m++].fun(a, b, c);
node[m++].fun(a, c, b);
node[m++].fun(b, a, c);
node[m++].fun(b, c, a);
node[m++].fun(c, a, b);
node[m++].fun(c, b, a);
}
sort(node,node+m,cmp);
int maxlen=0;
memset(dp,0,sizeof(dp));
for(int i=0;i<m;i++)
{
dp[i]=node[i].h;
for(int j=0;j<i;j++)
if(node[i].x>node[j].x&&node[i].y>node[j].y)
dp[i]=max(dp[i],dp[j]+node[i].h);
if(dp[i]>maxlen)
maxlen=dp[i];
}
cout<<"Case "<<++cnt<<": maximum height = "<<maxlen<<endl;
}
return 0;
}
[UVA 1347 | POJ 2677] Tour
题目描述
John Doe是一名出色的飞行员。一次,他决定租一架小飞机开始旅行一些美丽的地方。John Doe为自己设计的飞行路线满足以下要求:
1>路线经过所有的城市;2>路线从最左边的地方开始,先严格向右,到达最右边的地方后,再严格向左回到出发的地方;3>两个地点之间的路线是直线。
现在,给出每一个点的坐标,请你求出满足要求的最短路线的长度。
一句话题意:有n个点,给出x、y坐标。找出一条路,从最左边的点出发,严格向右走到达最右点再严格向左回到最左点。问最短路径的长度是多少?
输入
输入文件包含多组数据。
每一组数据的第1行包含一个整数n (1<=n<=1000),表示点的数量。接下来的n行,每行包含两个浮点数(double) xi,yi,表示一个点的坐标为(xi,yi)。
输出
对于每一组测试数据,输出一个两位小数,表示你计算出的最短距离。
样例数据
样例输入 | 样例输出 |
3 |
6.47 |
解析
<题目类型:双调欧几里得旅行商问题>
1.首先需要将原问题转化为,两个人A、B同时从最左边的点出发,一起严格向最右点走,且经过所有点一次(除了最左点和最右点)。这两个问题具有等价性。
2.先自然想到用dp(i,j)表示A走到i,B走到j时的状态还需要走多远到终点(注意表示的是还有多少到终点,所以其结果与前面怎么走的无关),那么可以证明dp(i,j)==dp(j,i);这里有的人可能会疑惑为什么会相等,刚刚说过dp(i,j)表示已经达到这个状态后还需要走多远到达终点,与怎么到达这个状态的并没有关系,所以dp(i,j)和dp(j,i)只是两个人角色对换了而已。
3.想到这一步之后,会出现一个问题,就是dp(i,j)无法知道i、j之间的某些点是否已经走过了,所以我们需要进一步思考,刚刚我们提到,dp(i,j)==dp(j,i),那么我们就可以始终让i>=j(等于只有终点和起点达到)。如果j>i了,只需要交换A、B的角色即可,即将i换为j,j换为i。
4.有了这个条件之后,我们就可以规定dp(i,j)规定为:A在i,B在j(i>=j)且i之前的所有点都走过了,这样也不会漏解,为什么呢?我们的自然的方法中,之所以i~j之间有点不知道走过了没,就是因为我们允许A连续走了多步,比如A从P1->P5->P6,而B可能从P1->P2。所以P3,P4我们不知道有没有被A或者B走到,因为我们只知道A走到了P6而B走到了P2。但是你明显发现了,在刚刚那个例子中,P3、P4之后必须要被B走到。所以我们改进的dp(i,j)中可以让A和B一格一格走,要么A走,要么B走(其实只是让顺序变化了一下而已)。
5.有了刚刚的论证,我们的状态转移就变成了下面这样:
dp[i][j]=min(DP(i+1,j)+dist(i,i+1),DP(i+1,i)+dist(j,i+1));
即要么A走,要么B走,如果A走的话,那么走到状态dp(i+1,j);如果B走,那么走到状态dp(i,i+1)到要求前面大于后面,所以dp(i,i+1)==dp(i+1,i)即可。注意dist(i,j)表示i-j的距离。
Code
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
struct point
{
double x;
double y;
};
point p[1010];
double dp[1010][1010];
double dis[1010][1010];
bool cmp(point a,point b)
{
return a.x<b.x;
}
double dist(int i,int j)
{
if(dis[i][j]>=0)
return dis[i][j];
return dis[i][j]=sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y));
}
double DP(int i,int j)
{
if(dp[i][j]>=0)
return dp[i][j];
dp[i][j]=min(DP(i+1,j)+dist(i,i+1),DP(i+1,i)+dist(j,i+1));
return dp[i][j];
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<1010;i++)
for(int j=0;j<1010;j++)
{
dis[i][j]=-1.0;
dp[i][j]=-1.0;
}
for(int i=0;i<n;i++)
cin>>p[i].x>>p[i].y;
sort(p,p+n,cmp);
for(int j=0;j<n;j++)
dp[n-2][j]=dist(n-2,n-1)+dist(j,n-1);
printf("%.2lf\\n",DP(0,0));
}
return 0;
}
[UVA 12563] Jin Ge Jin Qu
题目描述
有一首很热门的曲子,叫做"劲歌金曲"。这首歌实际上是37首歌的集合,长达11分18秒。为什么它这么热门呢?假设你在KTV唱歌时只有15秒就到包场时间了,由于KTV不会在唱歌中途来叫停,你应该尽快选另一首曲子来延长时间。如果这时你选了劲歌金曲,那么你就会得到额外663秒的时间......~\\(≧▽≦)/~
现在你还有一些时间,但是你准备制定一个计划。同时你要满足以下规则:
1>一首歌最多只能唱一遍(包括 劲歌金曲 )
2>对于一首长度为t的歌,要么唱完t时间,要么不唱
3>一首歌结束后,立即唱下一首(中间没有停顿)
你的目标很简单,唱尽可能多的歌,尽可能晚的离开KTV根据第三条规则,这也会使我们唱最多的歌)。
输入
输入文件的第一行包含一个整数T (1<=T<=30),表示有T组测试数据。
每一组测试数据以两个整数n和t (1≤n≤50,1≤t≤10^9)开始,分别表示歌曲的数量(不包括劲歌金曲)和剩余的时间。接下来的一行包含n个整数,分别表示这n首歌的时间长度 (以秒(s)为单位,每首歌的长度不超过3分钟)。
输入数据保证,所有歌(包括劲歌金曲)的时间总和一定超过t。
输出
对于每一组数据,给出最大的歌曲数和唱歌的总时间。
样例数据
样例输入 | 样例输出 |
2 |
Case 1: 2 758 |
<样例解释>
对于第一组数据,先唱80秒长的第三首,再唱678秒长的劲歌金曲。
对于第二组数据,先唱第一首和第二首(总共99秒),此时还剩余最后1秒,我们再唱劲歌金曲(678秒)。如果我们先唱第一首和第三首(总共100秒),我们就没有时间唱劲歌金曲了。
解析
每首歌最多选一次,由条件180n+678>T可知最大T=9678s,可以转化为0-1背包的问题:
1.状态d[i][j]表示:在当前剩余时间为j的情况下,从i,i+1,…,n中能选出歌的最大数目。
状态转移方程:d[i][j]=max{ d[i+1][j] , d[i+1][j-t[i]]+1 },( j-t[i]>0 );其中d[i+1][j]表示第i首歌未选时所选歌的最大数目,d[i+1][j-t[i]]+1表示第i首歌被选择后所选歌的最大数目。注意当 j-t[i]<=0 时 ,即剩余时间不大于0时,第i首歌不能选,此时d[i][j]=d[i+1][j];
边界条件是:i>n,d[i][j]=0;
2.由于题目要求在所点歌数目最大的情况下尽量保证唱歌的时间最长,那么同样可以转化成0-1背包问题,但是d[i][j]要先计算:
状态song[i][j]表示:在当前剩余时间为j的情况下,从i,i+1,…,n中所选出歌累计的最长时间。
状态转移跟随d[i][j]进行:令v1=d[i+1][j](即不选第i首歌),v2=d[i+1][j-t[i]]+1(选择第i首歌)
如果:
1) v2>v1, 说明第i首歌必须点,song[i][j]=song[i+1][j-t[i]]+t[i];
2) v2==v1, song[i][j]=max{song[i+1][j],song[i+1][j-t[i]]+t[i]};
3) v2<v1, 说明第i首歌一定不能点,song[i][j]=song[i+1][j];
逆序递推,答案是d[1][T]和song[1][T]。
Code
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int INF=-100000000; const int maxn=50; const int maxt=10000; int t[maxn+5]; int d[maxn+5][maxt]; int song[maxn+5][maxt]; int n,T; int main() { int Case; scanf("%d",&Case); for(int tt=1;tt<=Case;tt++) { scanf("%d%d",&n,&T); memset(t,0,sizeof t); for(int i=1;i<=n;i++) scanf("%d",&t[i]); memset(d,0,sizeof d); memset(song,0,sizeof song); for(int j=T;j>=0;j--) { if(j-t[n]>0) song[n][j]=t[n]; else song[n][j]=0; } for(int i=n;i>=1;i--) for(int j=T;j>0;j--) { int v1,v2; v1=d[i+1][j]; if(j-t[i]<=0) v2=INF; else v2=d[i+1][j-t[i]]+1; d[i][j]=max(v1,v2); if(v2>v1) song[i][j]=song[i+1][j-t[i]]+t[i]; else if(v2==v1) song[i][j]=max(song[i+1][j],song[i+1][j-t[i]]+t[i]); else song[i][j]=song[i+1][j]; } int num=d[1][T]+1; int len=song[1][T]+678; printf("Case %d: %d %d\\n",tt,num,len); } return 0; }
[UVA 11400] Lighting System Design
题目描述
你将要为一个会议大厅设计一个照明系统。在做了一些调查和计算之后,你发现有一个节能的设计能满足大厅的照明需求。根据这一设计,你需要n种不同功率的电灯。由于电流调节需要,所有的电灯都需要被通过相同的电流,因此,每一种灯都有对应的额定电压。现在,你已经知道了每一种电灯的数量和单位成本。但问题来了,你将要为所有类别的灯泡买同样的电源。事实上,你也可以为每一种灯泡单独买一种电源(我们认为:一个电源可以为无数个额定电压为电源电压的电灯供电)来完成设计。但是公司财务部很快发现他们可以通过删除一些电源并更换高功率的灯泡。你当然不能把灯泡换成低功率的,因为这样就会使大厅的一部分不能得到照明。你更关心的是节约金钱而不是节约能源,因此你要重新设计一个系统(将一些低电压灯泡更换为高电压灯泡),来使价格最便宜。
输入
有多组数据。
每一组数据以一个整数n (1<=n<=1000),表示灯泡的种类。接下来的n行每一行表示一种灯泡的信息,一行包含4个整数:额定电压V (1<=V<=132000),满足所需电压的电源的单价K (1<=K<=1000),灯泡的单价C (1<=C<=10),需要的灯泡数量L (1<=L<=100)。
当n=0时,输入数据结束。
输出
对于每一组数据,输出可能的最小花费。
样例数据
样例输入 | 样例输出 |
3 |
778 |
解析
首先需要明确一种灯泡要么全部换,要么不换。如果换一部分的话,首先电源费用得不到节约,那么节约的部分就只来自于换的那部分灯泡,既然可以节约钱干嘛不干脆全部换了呢?所以要么全换,要么不换。然后我们的算法就是先按照V排序,然后cost[i]表示解决前 i 种灯泡的最优解,那么转移方程是枚举j<i,将j之前的保持最优解cost[j]不变,j之后的全部变成i种灯泡。开始有一个疑问是:会不会漏解,为什么没有枚举替换j之前的不连续的一部分?后来发现,这个问题其实不存在,因为i之前的灯泡肯定是越后面的花费越大,因为如果前面的花费反而更大的话,大可以转换为后面的灯泡。
Code
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #define INF 0x3f3f3f3f #define MAXN 1010 using namespace std; struct node { int v,k,c,l; }; node light[MAXN]; bool cmp(node a,node b) { return a.v<b.v; } int num[MAXN]; int cost[MAXN]; int main() { int n; while(scanf("%d",&n)!=EOF&&n!=0) { for(int i=1;i<=n;i++) cin>>light[i].v>>light[i].k>>light[i].c>>light[i].l; sort(light+1,light+n+1[SinGuLaRiTy] 贪心题目复习