DP百题练

Posted lpf-666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DP百题练相关的知识,希望对你有一定的参考价值。

DP百题练

鉴于本人 DP 太弱了,决定下定决心,连刷 100 题 DP。
题单见下:

  1. 初级 DP
  2. 中阶 DP
  3. 高阶 DP
    咬紧牙关,争取在暑假之前完成练习。

题解大全

第1题(2020.3.13)

移动服务
DP水题,转移方程随便推。
首先设 (f[i][x][y][z]) 表示已经解决了 (i) 个问题,三个人的位置分别为 (x,y,z),则有转移方程为:

(f[i+1][p_{i+1}][y][z]=min(f[i+1][p_{i+1}][y][z]),f[i][x][y][z]+c[x][p_{i+1}])

(f[i+1][x][p_{i+1}][z]=min(f[i+1][x][p_{i+1}][z]),f[i][x][y][z]+c[y][p_{i+1}])

(f[i+1][x][y][p_{i+1}]=min(f[i+1][x][y][p_{i+1}]),f[i][x][y][z]+c[z][p_{i+1}])

但是显然时间上((O(LN^3)))和空间上都过不去,于是考虑缩小一维。

(f[i][x][y]) 表示已近解决了 (i) 个问题,其中一人的位置为 (p_{i}) ,另外两个的位置为 (x,y)

(f[i+1][x][y]=min(f[i+1][x][y],f[i][x][y]+c[p_i][p_{i+1}]))

(f[i+1][p_i][y]=min(f[i+1][p_i][y],f[i][x][y]+c[x][p_{i+1}]))

(f[i+1][x][p_i]=min(f[i+1][x][p_i],f[i][x][y]+c[y][p_{i+1}]))

按照题目要求,再判断 (x!=y!=p_i) 就好了,答案为 (min_{x!=y!=p_n}){(f[n][x][y])}。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iostream>
#define N 1005
#define L 205
using namespace std;

int n,l,c[L][L],p[N],f[N][L][L];

int main(){
    scanf("%d %d",&l,&n);
    for(int i=1;i<=l;i++)
        for(int j=1;j<=l;j++)
            scanf("%d",&c[i][j]);
    for(int i=1;i<=n;i++) scanf("%d",&p[i]);
    memset(f,0x3f,sizeof(f));
    f[0][1][2]=0;
    p[0]=3;
    for(int i=0;i<n;i++)
        for(int x=1;x<=l;x++)
            for(int y=1;y<=l;y++)
                if(x!=p[i] && y!=p[i] && x!=y){
                    f[i+1][x][y]=min(f[i+1][x][y],f[i][x][y]+c[p[i]][p[i+1]]);
                    f[i+1][p[i]][y]=min(f[i+1][p[i]][y],f[i][x][y]+c[x][p[i+1]]);
                    f[i+1][x][p[i]]=min(f[i+1][x][p[i]],f[i][x][y]+c[y][p[i+1]]);
                }
    int ans=0x3f3f3f3f;
    for(int i=1;i<=l;i++){
        for(int j=1;j<=l;j++){
            if(i!=j && i!=p[n] && j!=p[n])ans=min(ans,f[n][i][j]);
        }
    }
    printf("%d
",ans);
    return 0;
}

第2题 (2020.3.13)

传纸条

首先明白来回传纸条等于一次传两张纸条。

然后完整一下题意:当两张纸条同时传到一个格子时,那个格子的值只加一次。

再然后设 (f[x1][y1][x2][y2]) 表示第一张纸条传到 ((x1,y1)) ,第二张纸条传到 ((x2,y2))。则:

(f[x1][y1][x2][y2]=min(f[x1-1][y1][x2-1][y2],f[x1-1][y1][x2][y2-1],f[x1][y1-1][x2][y2-1],f[x1][y1-1][x2-1][y2-1])+a[x1][y1]+a[x2][y2])

时间是 (O(N^2*M^2)) 的,容易炸(这题数据很水,可能能过)

#include <iostream>
#define maxn 55
using namespace std;
int f[maxn][maxn][maxn][maxn],a[maxn][maxn];
int n,m;
int max_ele(int a,int b,int c,int d){
    if (b>a)
        a = b;
    if (c>a)
        a = c;
    if (d>a)
        a = d;
    return a;
}
int main(){
    cin >> n >> m;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++) 
                cin >> a[i][j];
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            for (int k=1;k<=n;k++)
                for (int l=j+1;l<=m;l++) //这里避免了 j==l,即避免了重合的情况发生
                    f[i][j][k][l]=max_ele(f[i][j-1][k-1][l],f[i-1][j][k][l-1],f[i][j-1][k][l-1],f[i-1][j][k-1][l])+a[i][j]+a[k][l];
    cout << f[n][m-1][n-1][m] << endl;
    return 0;
}

然后想优化,发现和上一题思路类似,用一维表示路径,然后可以找规律省下两维。

(f[i][x1][x2]) 表示:走了 (i) 步,两张纸条的横坐标分别是 (x1,x2),易得 (x1+y1=x2+y2=i+1)

注意,没有走动时算 (i=1) ,则:

(f[i][x1][x2]=max(f[i-1][x1-1][x2-1],f[i-1][x1-1][x2],f[i-1][x1][x2-1],f[i-1][x1][x2])+a[x1][y1]+a[x2][y2])

时间复杂度 (O((N+M)*N^2)),有了很大的改善,同时要注意注意特判重合的情况。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#define N 55
using namespace std;

int n,m,a[N][N],f[N*2][N][N];

int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    for(int i=1;i<=m+n-1;i++){
        for(int x1=1;x1<=n;x1++){
            for(int x2=1;x2<=n;x2++){
                int y1=i+1-x1;
                int y2=i+1-x2;
                if(y1<1 || y2<1 || y1>m || y2>m) continue;
                f[i][x1][x2]=max(max(f[i-1][x1-1][x2-1],f[i-1][x1-1][x2]),max(f[i-1][x1][x2-1],f[i-1][x1][x2]))+a[x1][y1]+a[x2][y2];
                if(x1==x2){// 特判重合情况
                    f[i][x1][x2]-=a[x1][y1];
                }
            }
        }
    }
    printf("%d
",f[n+m-1][n][n]);
    return 0;
}

第3题 (2020.3.13)

I-区域

真 · 神题(码量不是一般的大)

考虑到一个凸多边形一定是连续的多行组成。可以考虑每行选哪几个格子。

设 0为单调伸长, 1为单调伸短。

(f[i][j][l][r][x (0/1)][y (0/1)]) 为第 i 行,已经选出j个格子,第i行选择了[l,r] 区间的最大值。左右端点x,y分别为单调伸长 / 单调伸短 的最大权值。

状态转移:

  1. 若 x=0,y=0,则 (f[i][j][l][r][x][y]=max(f[i?1][j?(r?l+1)][l′][r′][0][0])+cost(i,l,r)) ,其中(l<=l′<=r′<=r,j>=r?l+1)

  2. 若 x=0,y=1,则 (f[i][j][l][r][x][y]=max(f[i?1][j?(r?l+1)][l′][r′][0][0/1])+cost(i,l,r)),其中(l<=l′<=r<=r′,j>=r?l+1)

  3. 若 x=1,y=0, 则 (f[i][j][l][r][x][y]=max(f[i?1][j?(r?l+1)][l′][r′][0/1][0])+cost(i,l,r)),其中(l′<=l<=r′<=r,j>=r?l+1)
  4. 若 x=1,y=1, 则 (f[i][j][l][r][x][y]=max(f[i?1][j?(r?l+1)][l′][r′][0/1][0/1])+cost(i,l,r)),其中(l′<=l<=r<=r′,j>=r?l+1)

初始状态:$ f[i][0][l][r][0][0]=0(0<=i<=n,1<=l<=r<=m)$,其余为负无穷。

目标:(maxf[i][k][l][r][0/1][0/1](1<=i<=n))

输出方案只需通过状态转移延展即可。(cost(i,l,r))用前缀和计算

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 16;

struct S{
    int i, j, l, r, x, y;
}pre[N][N * N][N][N][2][2], t;

int n, m, k, a[N][N], f[N][N * N][N][N][2][2];
int ans = 0;

int inline cost(int i, int l, int r){
    return a[i][r] - a[i][l - 1];
}

void print(S x){
    if(x.j == 0) return;
    print(pre[x.i][x.j][x.l][x.r][x.x][x.y]);
    for(int i = x.l; i <= x.r; i++) 
        printf("%d %d
", x.i, i);
}

int main(){
    memset(f, 0xcf, sizeof f);

    scanf("%d%d%d", &n, &m, &k);
    for(int r = 0; r <= n; r++) {
        for(int i = 1; i <= m; i++){
            for(int j = i; j <= m; j++)
                f[r][0][i][j][0][0] = 0;
        }
    }
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            scanf("%d", &a[i][j]), a[i][j] += a[i][j - 1];

    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= k; j++){
            for(int l = 1; l <= m; l++){
                for(int r = l; r <= m; r++){
                    if(j < r - l + 1) continue;

                    //x = 0, y = 0;
                    for(int l1 = l; l1 <= r; l1++){
                        for(int r1 = l1; r1 <= r; r1++){
                            int &v = f[i][j][l][r][0][0], val = f[i - 1][j - (r - l + 1)][l1][r1][0][0] + cost(i, l, r);
                            if(v < val) {
                                v = val, pre[i][j][l][r][0][0] = (S){i - 1, j - (r - l + 1), l1, r1, 0, 0};
                            }
                        }
                    }
                    //x = 0, y = 1;
                    for(int l1 = l; l1 <= r; l1++){
                        for(int r1 = r; r1 <= m; r1++){
                            for(int y1 = 0; y1 < 2; y1++) {
                                int &v = f[i][j][l][r][0][1], val = f[i - 1][j - (r - l + 1)][l1][r1][0][y1] + cost(i, l, r);
                                if(v < val) {
                                    v = val, pre[i][j][l][r][0][1] = (S){i - 1, j - (r - l + 1), l1, r1, 0, y1};
                                }
                            }
                        }
                    }

                    // x = 1, y = 0;
                    for(int l1 = 1; l1 <= l; l1++){
                        for(int r1 = l; r1 <= r; r1++){
                            for(int x1 = 0; x1 < 2; x1++) {
                                int &v = f[i][j][l][r][1][0], val = f[i - 1][j - (r - l + 1)][l1][r1][x1][0] + cost(i, l, r);
                                if(v < val) {
                                    v = val, pre[i][j][l][r][1][0] = (S){i - 1, j - (r - l + 1), l1, r1, x1, 0};
                                }   
                            }
                        }
                    }

                    // x = 1, y = 1;
                    for(int l1 = 1; l1 <= l; l1++){
                        for(int r1 = r; r1 <= m; r1++){
                            for(int x1 = 0; x1 < 2; x1++) {
                                for(int y1 = 0; y1 < 2; y1++) {
                                    int &v = f[i][j][l][r][1][1], val =  f[i - 1][j - (r - l + 1)][l1][r1][x1][y1] + cost(i, l, r);
                                    if(v < val) {
                                        v = val, pre[i][j][l][r][1][1] = (S){i - 1, j - (r - l + 1), l1, r1, x1, y1};
                                    }
                                }
                            }
                        }
                    }
                    if(j == k){
                        for(int x = 0; x < 2; x++) {
                            for(int y = 0; y < 2; y++) {
                                if(ans < f[i][j][l][r][x][y]) {
                                    ans = f[i][j][l][r][x][y], t = (S){i, j, l, r, x, y};
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    printf("Oil : %d
", ans);
    print(t);
    return 0;   
}

以上是关于DP百题练的主要内容,如果未能解决你的问题,请参考以下文章

DBSDFZOJ-----1003-----语法百题-----余数

LeetCode第一百题—相同的数—Python实现

numpy百题冲关,pandas百题冲关

三日一练-C语言百题(007)

4.13趣味百题第七题

图像处理一百题(1-10)陆续更新