【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,cmp); num[0]=0; for(int i=1;i<=n;i++) { num[i]=num[i-1]+light[i].l; } cost[0]=0; for(int i=1;i<=n;i++) { cost[i]=INF; for(int j=0;j<=i;j++) cost[i]=min(cost[i],cost[j]+(num[i]-num[j])*light[i].c+light[i].k); } cout<<cost[n]<<endl; } return 0; }
[UVA 1625] Color Length
题目描述
输入两个长度分别为n和m(n,m≤5000)的颜色序列,要求按序列合并成同一个序列,即每次可以把一个序列开头的颜色放到新序列的尾部。例如,两个颜色序列GBBY和YRRGB,至少有两种合并结果:GBYBRYRGB和YRRGGBBYB。对于每个颜色c来说,其跨度L(c)等于最大位置和最小位置之差。例如,对于上面两种合并结果,每个颜色的L(c)和所有L(c)的总和如图所示。你的任务是找一种合并方式,使得所有L(c)的总和最小。(注:该英文翻译来自《算法竞赛入门经典(第2版)》)
输入
输入文件包含了T组测试数据,T在输入数据的第1行会给出。
每一组测试数据包含两行字符串,各代表一个颜色序列。在字符串中,颜色用大写英文字母表示。
输入数据保证:每组数据中出现的颜色数不超过26,每一个颜色序列的长度不超过5000。
输出
对于每一组测试数据,输出一个整数,表示L(c)的总和的最小值。
样例数据
| 样例输入 | 样例输出 |
|
2 |
10 |
解析
对于两个颜色序列p和q,设d(i,j),表示p拿前i个字符,q拿前j个字符所要的代价。
由于n,m<=5000,二维数组改成滚动数组。
这个时候,不是等到一个颜色全部移动完了之后再算跨度,而是,只要多少种颜色已经开始但尚未结束,就L(c)+1;
重点在于求代价C。首先计算全部移动q,只要是该字符开头,代价就加一,但是如果刚好是最后一个就恢复。然后再推数组p时,就可以直接利用已经计算好的c代价数组,只需要根据它更新由于i的加入而增加的代价。
Code
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #define maxn 5005 #define INF 0x3f3f3f3f using namespace std; char p[maxn],q[maxn]; int sp[26],ep[26],sq[26],eq[26]; int d[2][maxn],c[2][maxn]; int main() { int t; scanf("%d",&t); while(t--) { scanf("%s%s",p+1,q+1); int n=strlen(p+1); int m=strlen(q+1); for(int i=1;i<=n;i++) p[i]-='A'; for(int i=1;i<=m;i++) q[i]-='A'; for(int i=0;i<26;i++) { sp[i]=sq[i]=INF; ep[i]=eq[i]=0; } for(int i=1;i<=n;i++) { sp[p[i]]=min(sp[p[i]],i); ep[p[i]]=i; } for(int i=1;i<=m;i++) { sq[q[i]]=min(sq[q[i]],i); eq[q[i]]=i; } memset(c,0,sizeof(c)); memset(d,0,sizeof(d)); int t=1; for(int i=0;i<=n;i++) { for(int j=0; j<=m;j++) { if(!i&&!j) continue; int v1=INF,v2=INF; if(i) v1=d[t^1][j]+c[t^1][j]; if(j) v2=d[t][j-1]+c[t][j-1]; d[t][j]=min(v1, v2); if(i) { c[t][j]=c[t^1][j]; if(sp[p[i]]==i&&sq[p[i]]>j) c[t][j]++; if(ep[p[i]]==i&&eq[p[i]]<=j) c[t][j]--; } else if(j) { c[t][j]=c[t][j-1]; if(sq[q[j]]==j&&sp[q[j]]>i) c[t][j]++; if(eq[q[j]]==j&&ep[q[j]]<=i) c[t][j]--; } } t^=1; } printf("%d\n",d[t^1][m]); } return 0; }
[UVA 10003] Cutting Sticks
题目描述
你的任务是替一家叫Analog Cutting Machinery (ACM)的公司切割木棍。 切割木棍的成本是根据木棍的长度而定。 而且切割木棍的时候每次只切一段。
很显然的,不同切割的顺序会有不同的成本。 例如: 有一根长10公尺的木棍必须在第2、4、7公尺的地方切割。 这个时候就有几种选择了。你可以选择先切2公尺的地方, 然后切4公尺的地方,最后切7公尺的地方。这样的选择其成本为:10+8+6=24。 因为第一次切时木棍长10公尺,第二次切时木棍长8公尺,第三次切时木棍长6公尺。 但是如果你选择先切4公尺的地方,然后切2公尺的地方,最后切7公尺的地方, 其成本为:10+4+6=20,这成本就是一个较好的选择。
你的老板相信你的电脑能力一定可以找出切割一木棍所需最小的成本。
一句话题意:给定一根已知长度的木棍,给定n个切割点,要求按照切割点切割木棍,花费按照切割的木棍长度计算,例如有一根长10的木棍,切割点为2、4、7,如果按照2、4、7的顺序切割,花费将是10 + 8 + 6 = 24,如果按照4、2、7的顺序切割,那么花费将是10 + 4 + 6 = 20,切割顺序可以任意,要求花费最小。
输入
包含多组测试数据。
对于每组测试数据:第1行包含一个正整数l (l<1000),表示木棍的总长度。第2行给出正整数n (n<50),表示切割点的数量。第3行按升序给出n个正整数ci (0<ci<l),表示每一个切割点的位置。
当l=0时,输入数据结束。
输出
对于每一组测试数据,输出完成切割的最小花费。输出格式见样例。
样例数据
| 样例输入 | 样例输出 |
|
100 |
The minimum cutting is 200. |
解析
比较典型的动态规划题目,根据题意找到状态转移公式就好了:dp[i][j]=max{dp[i][k]+dp[k][j]+len[j]-len[i]|i<k<j}
Code
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<algorithm> #include<cstring> const int INF = 0x3f3f3f3f; using namespace std; int dp[100][100]; int num[100]; int main() { int len,n; while(scanf("%d",&len)&&len) { scanf("%d",&n); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) scanf("%d",&num[i]); num[0]=0; num[n+1]=len; int minn,p; for(int i=1;i<=n+1;i++) { for(int j=0;j+i<=n+1;j++) { p=j+i; minn=INF; for(int k=j+1;k<p;k++) { int temp=dp[j][k]+dp[k][p]+num[p]-num[j]; if(temp<minn) minn=temp; } if(minn!=INF) dp[j][p]=minn; } } printf("The minimum cutting is %d.\n",dp[0][n+1]); } return 0; }
[POJ 1141] Brackets Sequence
题目描述
我们认为一个括号序列是有规律的,需满足以下条件:
1.一个空的序列是有规律的;
2.如果S是有规律的括号序列,那么(S)和[S]都是有规律的括号序列;
3.如果A和B都是有规律的括号序列,那么AB也是有规律的括号序列。
举个例子,一下的所有括号序列都是有规律的:
(), [], (()), ([]), ()[], ()[()]
而以下的括号序列都不是:
(, [, ), )(, ([)], ([(]
给出一个包含'(', ')', '[', 和 ']'的序列S,你要找到最短的有规律的括号序列,使S成为其字串。
输入
输入文件最多包含100个括号字符(仅包含'(', ')', '[', 和 ']')。
输出
输出找到的括号序列。
样例数据
| 样例输入 | 样例输出 |
| ([(] | ()[()] |
解析
用DP求最少需要括号数:以p从1到n(字符串长度),记录下从i到i+p需要添加的最少括号数f[i][j],同时记录下中间需要添加括号的位置pos[i][j]——为-1表示不需要添加。
Code
#include<cstdio> #include<cstring> #define MAXN 120 const int INF=0x7fffffff; int f[MAXN][MAXN],pos[MAXN][MAXN]; char s[MAXN]; int n; int DP() { n=strlen(s); memset(f,0,sizeof(f)); for(int i=n;i>0;i--) { s[i]=s[i-1]; f[i][i]=1; } int tmp; for(int p=1;p<=n;p++) { for(int i=1;i<=n-p;i++) { int j=i+p; f[i][j]=INF; if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']')) { tmp=f[i+1][j-1]; if(tmp<f[i][j]) f[i][j]=tmp; } pos[i][j]=-1; for(int k=i;k<j;k++) { tmp=f[i][k]+f[k+1][j]; if(tmp<f[i][j]) { f[i][j]=tmp; pos[i][j]=k; } } } } return f[1][n]; } void print(int beg,int End) { if(beg>End) return ; if(beg==End) { if(s[beg]=='('||s[beg]==')') printf("()"); else printf("[]"); } else { if(pos[beg][End]==-1) { if(s[beg]=='(') { printf("("); print(beg+1,End-1); printf(")"); } else { printf("["); print(beg+1,End-1); printf("]"); } } else { print(beg,pos[beg][End]); print(pos[beg][End]+1,End); } } } int main() { scanf("%s",s); DP(); print(1,n); return 0; }
<这里有一个坑一点的变式:UVALive 2451,你可以改改这道题的代码再提交,随意感受一下>
#include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<iostream> #include<cstdlib> #include<cmath> #define MAXN 120 using namespace std; const int INF=0x7fffffff; int f[MAXN][MAXN],pos[MAXN][MAXN]; char s[MAXN]; int n; void Clear() { memset(pos,0,sizeof(pos)); memset(s,0,sizeof(s)); n=0; } int DP() { n=strlen(s); memset(f,0,sizeof(f)); for(int i=n;i>0;i--) { s[i]=s[i-1]; f[i][i]=1; } int tmp; for(int p=1;p<=n;p++) { for(int i=1;i<=n-p;i++) { int j=i+p; f[i][j]=INF; if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']')) { tmp=f[i+1][j-1]; if(tmp<f[i][j]) f[i][j]=tmp; } pos[i][j]=-1; for(int k=i;k<j;k++) { tmp=f[i][k]+f[k+1][j]; if(tmp<f[i][j]) { f[i][j]=tmp; pos[i][j]=k; } } } } return f[1][n]; } void print(int beg,int End) { if(beg>End) return ; if(beg==End) { if(s[beg]=='('||s[beg]==')') printf("()"); else printf("[]"); } else { if(pos[beg][End]==-1) { if(s[beg]=='(') { printf("("); print(beg+1,End-1); printf(")"); } else { printf("["); print(beg+1,End-1); printf("]"); } } else { print(beg,pos[beg][End]); print(pos[beg][End]+1,End); } } } int main() { int num; scanf("%d",&num); getchar(); for(int i=1;i<=num;i++) { Clear(); gets(s); gets(s); DP(); print(1,n); if(i!=num) printf("\n\n"); else printf("\n"); } return 0; }