算法艺术与信息学竞赛 之 动态规划入门

之所以这次这么久没有更新,主要是在做一片动态规划的专题,因为几道例题之间是有着内在联系的,如果一道道的来讲,不能很好的体现他们的联系
首先我们来介绍动态规划(以下简称DP)的两种动机:

1:有大量重叠子问题。

下面我们看一道例题:
1183. Brackets Sequence
Time limit: 1.0 second
Memory limit: 64 MB
Let us define a regular brackets sequence in the following way:
Empty sequence is a regular sequence.
If S is a regular sequence, then (S) and [S] are both regular sequences.
If A and B are regular sequences, then AB is a regular sequence.
For example, all of the following sequences of characters are regular brackets sequences:
(), [], (()), ([]), ()[], ()[()]
And all of the following character sequences are not:
(, [, ), )(, ([)], ([(]
Some sequence of characters ‘(‘, ‘)’, ‘[‘, and ‘]’ is given. You are to find the shortest possible regular brackets sequence, that contains the given character sequence as a subsequence. Here, a string a1a2…an is called a subsequence of the string b1b2…bm, if there exist such indices 1 ≤ i1 < i2 < … < in ≤ m, that aj=bij for all 1 ≤ j ≤ n.
Input
The input contains at most 100 brackets (characters ‘(‘, ‘)’, ‘[’ and ‘]’) that are situated on a single line without any other characters among them.
Output
Write a single line that contains some regular brackets sequence that has the minimal possible length and contains the given sequence as a subsequence.
Sample
input output
([(]
()[()]
Problem Author: Andrew Stankevich
Problem Source: 2001-2002 ACM Northeastern European Regional Programming Contest

刚开始看的时候以为是括号匹配问题,要用栈,后来发现是要将括号补全并输出。
首先很容易由题目联想到递归的方式,记录d[i] [j] 表示第i个到第j个所需要添加的括号数逐步找内部的结构,例如:设s和s’是一个串

1:S形如(s')或[s'],则d[i][j]=d[i+1][j-1];
2:S形如(S'或[S',则d[i][j]=d[i+1][j]+1;
3:S形如S')或S'],则d[i][j]=d[i][j-1]+1;

这样就可以将问题重复化为子问题解决。
但是如果年轻的你非常耿直的直接写,你可能写出一个指数级别的算法,这显然是不行的,你可能已经发现,这个过程中你进行了大量的重复计算。
正常人可能就很自然的想到,我多算了,不如每个只算一遍,所以,每次算一遍之后,我们就将这个值存起来,那就不用算了!这是一个典型的用空间换时间的想法。
有没有办法可以不用多开空间呢?
我们可以尝试着去找这些式子算出来的规律,上面已经写过了,我们发现d[i][j]是需要d[i+1][j],d[i][j-1],d[i+1][j-1],中的一个的,我们必须有了这三个值才能确保算出d[i][j]。能否让我们以一定的顺序计算它们从而达到不需要记忆已经算过的值呢?
对于本题而言,它们的规律是区间长度上的,设i~j的距离为x,我们计算d[i][j]时,可能会用到的区间都是长度为x-1或x-2的,所以在这之前把所有长度为x-1的求出即可,这样按顺序递推出结果显然比记忆化搜索要优秀。
时间关系,也是才看到这个题的原题,还需要输出完整的括号序列。这个printf的操作我没有仔细思考,参考了一篇博客。
http://blog.csdn.net/jiange_zh/article/details/49994207

#include<bits/stdc++.h>
using namespace std;
#define INF 99999999
char s[120];
int dp[120][120];
int mark[120][120];
void printAns(int i,int j){
    if(i>j)return;
    if(i==j){
        if(s[i-1]=='('||s[i-1]==')')
            printf("()");
        else
            printf("[]");
        return;
    }
    if(mark[i][j]==-1){
        if(s[i-1]=='('){
            printf("(");
            printAns(i+1,j-1);
            printf(")");
        }
        else{
            printf("[");
            printAns(i+1, j-1);
            printf("]");
        }
    }
    else{
        printAns(i,mark[i][j]);
        printAns(mark[i][j]+1,j);
    }
}
int main(){
    gets(s);
    int n=strlen(s);
    if(!n)
        printf("\n");
    else{
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
            dp[i][i] = 1;
        for(int l=2;l<=n;l++){
            for (int i=1;i<=n-l+1;i++){
                int j=i+l-1;
                dp[i][j]=INF;
                if(s[i-1]=='('&&s[j-1]==')'||s[i-1]=='['&&s[j-1]==']')
                    if(dp[i+1][j-1]<dp[i][j])    
                        dp[i][j]=dp[i+1][j-1];
                mark[i][j]=-1;
                for(int k=i;k<j;k++)
                    if(dp[i][k]+dp[k+1][j]<dp[i][j]){
                        dp[i][j]=dp[i][k]+dp[k+1][j];
                        mark[i][j]=k;
                    }
            }
        }
        printAns(1,n);
        puts("");
    }
    return 0;
}

接下来我们再看一道例题,源于NOI99
棋盘分割
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 15767 Accepted: 5597
Description

将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了(n-1)次后,连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)
这里写图片描述

原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘,并使各矩形棋盘总分的均方差最小。
均方差,其中平均值,xi为第i块矩形棋盘的总分。
请编程对给出的棋盘及n,求出O’的最小值。
Input

第1行为一个整数n(1 < n < 15)。
第2行至第9行每行为8个小于100的非负整数,表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。
Output

仅一个数,为O’(四舍五入精确到小数点后三位)。
Sample Input

3
1 1 1 1 1 1 1 3
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0
1 1 1 1 1 1 0 3
Sample Output

1.633

首先我们还是要化简一下均方差的,因为打起来实在是太麻烦,这里直接写出结论吧。
均方差的平方=(∑xi^2)/n-(x平均值)^2。真的是难打啊,凑合看下吧。
然后是一个重要的概念,DP的状态。所谓状态就是字面的意思,代表你所开的dp数组的一种属性,这个概念我有点说不清楚,还是重在意会,拿这个题目来说,我们就要所开的DP数组是一个五维数组,表示一个范围(左上角横纵坐标和右下角横纵坐标以及当前分割次数),状态与状态之间是有着一定的联系的,我们将状态与状态之间的一种关系(多以方程的形式表示)称为状态转移方程。对于这个题目,记录f[x1][y1][x2][y2][k]是状态,再多开一个s[x1][y1][x2][y2]记录矩形权值和的平方,那么我们的状态转移方程可以这样写:

f[x1][y1][x2][y2][k]=min{min(f[x1][y1][i][y2][k-1])+s[i+1][y1][x2][y2],
f[i+1][y1][x2][y2][k-1])+s[x1][y1][i][y2]),
min(f[x1][y1][x2][j][k-1])+s[x1][j+1][x2][y2]),
f[x1][j][x2][y2][k-1])+s[x1][y1][x2][j])};

时间复杂度约为O(m^5 *n);
接下来上代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int sum[9][9][9][9];//记录平方和 
int dp[9][9][9][9][20];  
int num[9][9];
int main(){
    int n; 
    while(scanf("%d",&n)!=EOF){
        for(int i=1;i<=8;i++)
            for(int j=1;j<=8;j++)
                scanf("%d",&num[i][j]);
        for(int x1=1;x1<=8;x1++)
        for(int y1=1;y1<=8;y1++)
            for(int x2=1;x2<=8;x2++)
                for(int y2=1;y2<=8;y2++){
                    int t=0;
                    for(int i=x1;i<=x2;i++)
                        t+=num[i][y2];
                    if(y1==1&&x1==1)
                    sum[x1][y1][x2][y2]=sum[x1][y1][x2][y2-1]+t;  
                    else sum[x1][y1][x2][y2]=sum[1][1][x2][y2]-sum[1][1][x1-1][y2]-sum[1][1][x2][y1-1]+sum[1][1][x1-1][y1-1];  
                    dp[x1][y1][x2][y2][0]=sum[x1][y1][x2][y2]*sum[x1][y1][x2][y2];  
                }
        for(int k=1;k<n;k++)
            for(int x1=1;x1<=8;x1++)
                for(int y1=1;y1<=8;y1++)
                    for(int x2=1;x2<=8;x2++)
                        for(int y2=1;y2<=8;y2++){
                            int t=0x3f3f3f3f;
                            for(int a=x1;a<x2;a++){
                                int t1=min(dp[x1][y1][a][y2][k-1]+dp[a+1][y1][x2][y2][0],dp[a+1][y1][x2][y2][k-1]+dp[x1][y1][a][y2][0]);
                                t=min(t,t1);
                            }
                            for(int b=y1;b<y2;b++){
                                int t1=min(dp[x1][y1][x2][b][k-1]+dp[x1][b+1][x2][y2][0],dp[x1][b+1][x2][y2][k-1]+dp[x1][y1][x2][b][0]);  
                                t=min(t,t1);
                            }
                            dp[x1][y1][x2][y2][k]=t;
                        }
        double average=(double)(sum[1][1][8][8])/n;
        average*=average;
        double ans=((double)(dp[1][1][8][8][n-1])/n)-average;
        ans=sqrt(ans);
        printf("%.3lf\n",ans);
    }
    return 0;
}

MUSKET - Musketeers
no tags
In the time of Louis XIII and his powerful minister cardinal Richelieu in the Full Barrel Inn n musketeers had consumed their meal and were drinking wine. Wine had not run short and therefore the musketeers were eager to quarrel, a drunken brawl broke out, in which each musketeer insulted all the others.

A duel was inevitable. But who should fight who and in what order? They decided (for the first time since the brawl they had done something together) that they would stay in a circle and draw lots in order. A drawn musketeer fought against his neighbor to the right. A looser “quit the game” and to be more precise his corpse was taken away by servants. The next musketeer who stood beside the looser became the neighbor of a winner.

After years, when historians read memories of the winner they realized that a final result depended in a crucial extent on the order of duels. They noticed that a fence practice had indicated, who against who could win a duel. It appeared that (in mathematical language) the relation “A wins B” was not transitive! It could happen that the musketeer A fought better than B, B better than C and C better than A. Of course, among three of them the first duel influenced the final result. If A and B fight as the first, C wins eventually. But if B and C fight as the first, A wins finally. Historians fascinated by their discovery decided to verify which musketeers could survive. The fate of France and the whole civilized Europe indeed depended on that!

Task

N persons with consecutive numbers from 1 to n stay in a circle. They fight n-1 duels. In the first round one of these persons (e.g. with the number i) fights against its neighbor to the right, i.e. against the person numbered i+1 (or, if i=n, against the person numbered 1). A looser quits the game, and the circle is tighten so that the next person in order becomes a winner’s neighbor. We are given the table with possible duels results, in the form of a matrix. If Ai,j = 1 then the person with the number i always wins with the person j. If Ai,j = 0 the person i looses with j. We can say that the person k may win the game if there exists such a series of n-1 drawings, that k wins the final duel.
Write a program which:

reads matrix A from the standard input,
computes numbers of persons, who may win the game,
writes them into the standard output.
Input

The number of test cases t is in the first line of input, then t test cases follow separated by an empty line. In the first line of each test case integer n which satisfies the inequality 3<=n<=100 is written. In each of the following n lines appears one word consisting of n digits 0 or 1. A digit on j-th position in i-th line denote Ai,j. Of course Ai,j = 1 - Aj,i, for i<>j. We assume that Ai,i = 1, for each i.

Output

For each test case in the first line there should be written m - the number of persons, who may win the game. In the following m lines numbers of these persons should be written in ascending order, one number in each line.

Example

Sample input:
1
7
1111101
0101100
0111111
0001101
0000101
1101111
0100001

Sample output:
3
1
3
6

这道题比较有意思,与之前的类型看似不大一样,其实,按照DP的思路来说,这个题还算是比较容易的。
首先是设计状态,这个看上去很难,我们要将坐成一圈的人拉成链,每次会删除一个,显然,最后只剩下一个人的时候,他就是获胜者,那么我们可思考:对每一个人,他能够“遇到”那些人呢?,在刚开始,显然有一部分人是挨着的,接下来,如果i可以碰到j,那么必须要有一个在i,j之间的,i和j都可以碰到的人,且i和j中的一个能够打败他,i和j才能碰到一起,这就很像我们讲的第一题的思路了!实际上这就是一个区间问题,就只需要先枚举长度,然后类似的去做,注意在拉环成链时要注意取模的问题。具体看代码吧:

#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
using namespace std;
string s;
bool g[105][105],f[105][105];
int main(){
    int T;
    scanf("%d",&T);//处理输入的多组数据 
    while(T--){
        memset(g,0,sizeof(g));
        memset(f,0,sizeof(f));//有多组数据,每次要“刷新” 
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            cin>>s;
            for(int j=0;j<s.size();j++){
                if(s[j]=='1')
                    g[i][j]=true;
                else
                    g[i][j]=false;
            }
        }
        for(int i=0;i<n;i++)
            f[i][(i+1)%n]=true;//这里是dp的初始值,每个人都可以遇到他后面的那个人
        for(int len=2;len<=n;len++)//第一维枚举区间长度 
            for(int i=0;i<n;i++){//第二维枚举i 
                int j=i+len;//由i和len推出j 
                for(int k=i;k<=j;k++){//注意,序号从零开始 
                    int x=i%n,y=j%n,z=k%n;
                    if((f[x][z]==true && f[z][y]==true) && (g[x][z]==true || g[y][z]==true)){
                        f[x][y]=true;
                        continue;
                    }
                }
            }
        int ans=0;
        for(int i=0;i<n;i++)
            if(f[i][i])
                ans++;
        printf("%d\n",ans);
        for(int i=0;i<n;i++)
            if(f[i][i])
                printf("%d\n",i+1);
    }
    return 0;
}

这个代码样例是过了的,但是没有交过,有问题的话请大家指出!

下面介绍动态规划的第二个动机

2:用来解决多决策问题。

什么是多决策呢?我们来看下面这道题。

这一道题目比较有意思,大家可以自己试着按照我刚刚说的思路来看看
Mr. White, a fat man, now is crazy about a game named “Dance, Dance, Revolution”. But his
dance skill is so poor that he could not dance a dance, even if he dances arduously every time. Does
“DDR” just mean him a perfect method to squander his pounds? No way. He still expects that he
will be regarded as “Terpsichorean White” one day. So he is considering writing a program to plan the
movement sequence of his feet, so that he may save his strength on dancing. Now he looks forward to
dancing easily instead of sweatily.
“DDR” is a dancing game that requires the dancer to use
his feet to tread on the points according to the direction sequence
in the game. There are one central point and four side
points in the game. Those side points are classified as top,
left, bottom and right. For the sake of explanation, we mark
them integers. That is, the central point is 0, the top is 1,
the left is 2, the bottom is 3, and the right is 4, as the figure
on the right shows.
At the beginning the dancer’s two feet stay on the central
point. According to the direction sequence, the dancer has
to move one of his feet to the special points. For example, if
the sequence requires him to move to the top point at first,
he may move either of his feet from point 0 to point 1 (Note:
Not both of his feet). Also, if the sequence then requires him
to move to the bottom point, he may move either of his feet
to point 3, regardless whether to use the foot that stays on
point 0 or the one that stays on point 1.
There is a strange rule in the game: moving both of his feet to the same point is not allowed. For
instance, if the sequence requires the dancer to the bottom point and one of his feet already sta ys on
point 3, he should stay the very foot on the same point and tread again, instead of moving the other
one to point 3.
After dancing for a long time, Mr. White can calculate how much strength will be consumed when
he moves from one point to another. Moving one of his feet from the central point to any side points
will consume 2 units of his strength. Moving from one side point to another adjacent side point will
consume 3 units, such as from the top point to the left point. Moving from one side point to the
opposite side point will consume 4 units, such as from the top point to the bottom point. Yet, if he
stays on the same point and tread again, he will use 1 unit.
Assume that the sequence requires Mr. White to move to point 1 → 2 → 2 → 4. His feet may stays
on (point 0, point 0) → (0, 1) → (2, 1) → (2, 1) → (2, 4). In this couple of integers, the former number
represents the point of his left foot, and the latter represents the point of his right foot. In this way,
he has to consume 8 units of his strength. If he tries another pas, he will have to consume much more
strength. The 8 units of strength is the least cost.
Input
The input file will consist of a series of direction sequences. Each direction sequence contains a sequence
of numbers. Each number should either be ‘1’, ‘2’, ‘3’, or ‘4’, and each represents one of the four
directions. A value of ‘0’ in the direction sequence indicates the end of direction sequence. And this
value should be excluded from the direction sequence. The input file ends if the sequence contains a
single ‘0’.
Output
For each direction sequence, print the least units of strength will be consumed. The result should be a
single integer on a line by itself. Any more white spaces or blank lines are not allowable.
Sample Input
1 2 2 4 0
1 2 3 4 1 2 3 3 4 2 0
0
Sample Output
8
22

大意就是一个肥宅要玩跳舞机,一共有四个脚踩的地方(加上初始的地方是五个)移动脚是需要不同的体力值的,他怕累需要一个最优的方案。
这里我们非常容易的可以考虑到DP,首先来设计状态:
下一步怎么走取决于上一步的脚的位置,所以我们记录脚的位置是有必要的。
显然当前走了几步我们也是要记下来的,所以我们就可以确定开一个三维数组用来记录状态,即f[i][x][y]。
那么我们就可以开始递推了,这个递推关系非常明显,要么动左脚,要么动右脚,这既是我们刚刚在上面所讲到的“多决策”,每一步显然都是这样。
代码就非常好实现了,我写了两种方式的DP,有前到后和由后到前,这个是即将讲到的向前递推法和向后递推法,在本题中,他们各有优劣,我在vjudge上面交的时候时间是一样的。
首先我们来看看从前到后的方法:

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
int f[maxn][5][5],a[maxn];
int get_num(int x,int y){//用于计算从x移动到y所需消耗的体力值 
    if(x==y)return 1;  
    if(x==0||y==0)return 2;  
    if(x==2&&(y==1||y==3))return 3;  
    if(y==2&&(x==1||x==3))return 3;  
    if(x==1&&(y==4||y==2))return 3;  
    if(y==1&&(x==4||x==2))return 3;  
    if(x==3&&(y==2||y==4))return 3;  
    if(y==3&&(x==2||x==4))return 3;  
    if(x==4&&(y==1||y==3))return 3;  
    if(y==4&&(x==1||x==3))return 3;  
    return 4; 
}
int main(){
    while(scanf("%d",&a[1])&&a[1]){
        int x,n=1;
        while(scanf("%d",&x)&&x)
            a[++n]=x;//处理多组数据的输入,纪录长度为n; 
        memset(f,0x3f3f3f3f,sizeof(f));//重置f数组的值,因为我们要取的最优值是最小的,故初值应该赋为正无穷 
        f[0][0][0]=0;//最开始没有走一步时,消耗体力值显然为零,这是DP的一个边界,初始状态
        for(int i=1;i<=n;i++){//第一重循环枚举第几步  
            for(int x=0;x<=4;x++)//枚举上一步的左脚位置 
                for(int y=0;y<=4;y++){//枚举上一步的右脚位置 
                    f[i][a[i]][y]=min(f[i-1][x][y]+get_num(x,a[i]),f[i][a[i]][y]);//选择移动左脚 
                    f[i][x][a[i]]=min(f[i-1][x][y]+get_num(y,a[i]),f[i][x][a[i]]);//选择移动右脚 
                }
        }
        int ans=0x3f3f3f3f;
        for(int i=1;i<=4;i++){
            ans=min(ans,f[n][a[n]][i]);
            ans=min(ans,f[n][i][a[n]]);
        }
        printf("%d\n",ans);
    }
    return 0;
} 

这里可以注意到,由前向后的递推有一下优势:出状态比较简单,因为刚开始双脚都在中央。
但是它的末状态就有那么一点点的麻烦了,因为我们只能确定最后一只脚在那,另一只不确定,找答案是需要枚举。

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
int f[maxn][5][5],a[maxn];
int get_num(int x,int y){//用于计算从x移动到y所需消耗的体力值 
/*
    if(x==0)
        return 2;//从中间移动到旁边,耗体力值为2; 
    if(x==y)
        return 1;//不需要移动,原地踩一脚即可,耗体力值为1; 
    if(abs(x-y)==2)
        return 4;//移动到对面,耗体力值为4; 
    if(abs(x-y)==1)
        return 3;//移动到相邻的位置,耗体力值为3; 
*/
    if(x == y)return 1;  
    if(x == 0 || y == 0)return 2;  
    if(x == 2 && (y == 1 || y == 3) ) return 3;  
    if(y == 2 && (x == 1 || x == 3) ) return 3;  
    if(x == 1 && (y == 4 || y == 2) ) return 3;  
    if(y == 1 && (x == 4 || x == 2) ) return 3;  
    if(x == 3 && (y == 2 || y == 4) ) return 3;  
    if(y == 3 && (x == 2 || x == 4) ) return 3;  
    if(x == 4 && (y == 1 || y == 3) ) return 3;  
    if(y == 4 && (x == 1 || x == 3) ) return 3;  
    return 4;
}
int main(){
    while(scanf("%d",&a[1])&&a[1]){
        int x,n=1;
        while(scanf("%d",&x)&&x)
            a[++n]=x;//处理多组数据的输入,纪录长度为n; 
        memset(f,0x3f3f3f3f,sizeof(f));//重置f数组的值,因为我们要取的最优值是最小的,故初值应该赋为正无穷 
        for(int i=0;i<=4;i++){
            f[n+1][a[n]][i]=0;
            f[n+1][i][a[n]]=0;
        }//dp的初始状态 
        for(int i=n;i>=1;i--){//第一重循环枚举第几步 ,这里是逐步向前递推 
            for(int x=0;x<=4;x++)//枚举上一步的左脚位置 
                for(int y=0;y<=4;y++){//枚举上一步的右脚位置 
                    f[i][x][y]=min(f[i+1][x][a[i]]+get_num(y,a[i]),f[i+1][a[i]][y]+get_num(x,a[i]));
                }
        }
        printf("%d\n",f[1][0][0]);
    }
    return 0;
} 

这种从后往前的方法其实和之前的一样,只不过是初末状态的不同罢了,优缺点与上面恰好相反。

两组的时间一样,都是96ms,对于这个题来说都没什么区别。

接下来就是这篇博客的最后一道题了,NOI97积木游戏

【NOI1997】积木游戏

【题目描述】

SERCOI 最近设计了一种积木游戏。每个游戏者有N块编号依次为1 ,2,…,N的长方体积木。对于每块积木,它的三条不同的边分别称为”a边”、“b边”和”c边”,如下图所示:

这里写图片描述
游戏规则如下:
1、从N块积木中选出若干块,并将它们分成M(l<=M<=N) 堆,称为第1堆,第2 堆…,第M堆。每堆至少有1块积木,并且第K堆中任意一块积木的编号要大于第K+1堆中任意一块积木的编号(2<=K<=M)。
2.对于每一堆积木,游戏者要将它们垂直摞成一根柱子,并要求满足下面两个条件:
(1)除最顶上的一块积木外,任意一块积木的上表面同且仅同另一块积木的下表面接触,并且要求下面的积木的上表面能包含上面的积木的下表面,也就是说,要求下面的积木的上表面的两对边的长度分别大于等于上面的积木的两对边的长度。
(2)对于任意两块上下表面相接触的积木,下面的积木的编号要小于上面的积木的编号。
最后,根据每人所摞成的M根柱子的高度之和来决出胜负。
请你编一程序,寻找一种摞积木的方案,使得你所摞成的M根柱子的高度之和最大。

【输入】

第一行有两个正整数N和M(1<=M<=N<=100),分别表示积木总数和要求摞成的柱子数。这两个数之间用一个空格符隔开。接下来N行依次是编号从1到N的N个积木的尺寸,每行有三个;至1000之间的整数,分别表示该积木a边,b边和c边的长度。同一行相邻两个数之间用一个空格符隔开。

【输出】

一行,为一个整数,表示M根柱子的高度之和。

【输入样例】

4 2
10 5 5
8 7 7
2 2 2
6 6 6

【输出样例】

24

这道题折腾了我整整一个下午,本来想写书上写的4位向前递推的,可是很久也没有达到效果,转而写了三维的。
不按书上说的来,正常的思路就是设计一个三维状态,储存当前有几堆,顶端是几号,是哪个面。
注意,有人可能疑惑我们为什么要存面,其实,我们最开始的想法是存当前的面的长宽,但因为长宽的范围较大,我们无法存储,而转而存面的话,我们就可以很容易的存下,因为最多只有三个不同的面,所以这样的空间还是可以接受的。
关于递推的方式,其实这个题我们可以当做一个多决策问题,显然我们为了满足题目中的序号要求,就必须要按顺序决定每个积木怎么放,我们有三种决策:

1:放在当前堆的顶端,前提条件是要长宽满足,能放上去。
2:另起一堆,前提条件是当前堆数<m。
3:不使用当前积木。

这样便可以很快的求出答案了。
状态是O(n*m)转移为O(n),总时间为O(n^2*m)。
代码中的一些小的细节优化大家自己体会。

#include<bits/stdc++.h>
using namespace std; 
int n,m,t[105][3],f[105][105][3];
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
        scanf("%d%d%d",&t[i][0],&t[i][1],&t[i][2]);
    for(int i=1;i<=m;i++){
        for(int a=1;a<=n;a++){
            for(int b=0;b<a;b++){
                for(int k=0;k<=2;++k){
                    int a1=t[a][k],b1=t[a][(k+1)%3];
                    if(a1<b1)
                        swap(a1,b1);
                    for (int l=0;l<=2;++l){
                        int a2=t[b][l],b2=t[b][(l+1)%3];
                        if(a2<b2)
                            swap(a2,b2);
                        f[i][a][k]=max(f[i][a][k],f[i-1][b][l]+t[a][(k+2)%3]);
                        if(a1<=a2&&b1<=b2)
                            f[i][a][k]=max(f[i][a][k],f[i][b][l]+t[a][(k+2)%3]);
                    }
                }
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(max(f[m][i][0],f[m][i][1]),max(ans,f[m][i][2]));
    printf("%d",ans);
    return 0;
}

经历了一周多的时间,终于写完了这么一篇博客,上面的内容希望读者能够好好体会,如果有好的建议欢迎提出,下一章准备开始写DP常见模型分析,可能还会是一个大的专题,更新时间不能确定,见谅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值