DP刷题记录

Posted wyxdrqc

tags:

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

DP刷题记录

(本文例题目前大多数都选自算法竞赛进阶指南)

TYVJ1071

求两个序列的最长公共上升子序列

\(f_i,j\)表示a中的\(1-i\)与b中色\(1-j\)匹配时所能构成的以\(b_j\)结尾的最长公共上升子序列的长度

考虑转移
\[ f_i,j = \left\\beginarraylf_i - 1,j - 1 + 1\quad \quad \quad \quad \quad (a_i = b_j) \\ \max_k = 1^j - 1f_i - 1,k\quad \quad \quad (a_i \not = b_j) \endarray\right. \]
这种序列问题是通常可以通过设计强制结尾的选择的状态来进行转移的

这样直接DP的时间复杂度为\(n^3\)不能通过本题

发现时间复杂度主要在于第二个式子

我们考虑,对于同一个\(i\)\(j\)每加一,最多只会多出一个额外决策

所以我们每次从头重新扫一遍是非常慢的选择

因为大多数的决策都是共用的,所以我们就维护目前的最优决策值,\(j\)每加以,就更新当前的最优决策

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 3005;
int a[N],b[N];
int n;
int f[N][N];
inline int read()
    int v = 0,c = 1;char ch = getchar();
    while(!isdigit(ch))
        if(ch == '-') c = -1;
        ch = getchar();
    
    while(isdigit(ch))
        v = v * 10 + ch - 48;
        ch = getchar();
    
    return v * c;

int ans;
int main()
    n = read();
    for(int i = 1;i <= n;++i) a[i] = read();
    for(int i = 1;i <= n;++i) b[i] = read();
    for(int i = 1;i <= n;++i)
        int v = 0;
        for(int j = 1;j <= n;++j)
            if(a[i] == b[j]) f[i][j] = v + 1;
            else f[i][j] = f[i - 1][j];
            if(b[j] < a[i]) v = max(f[i - 1][j],v);
            ans = max(ans,f[i][j]);
        
    
    printf("%d\n",ans);
    return 0;

Poj3666

给定一个数组\(a\),构造一个单调不升或者单调不降的数组\(b\),最小化\(\sum_i = 1^n|a_i-b_i|\)

\(b\)中的数肯定在数组\(a\)中全部出现过

我们这里只讨论单调不降的情况

首先,我们比较容易设计出一个dp

\(f_i\)表示前\(i\)个数同时满足\(b_i = a_i\)的最小代价(因为\(b\)都是\(a\)中的数)

转移就枚举上一个数第一次选的位置\(k\)
\[ f_i = \min_k = 0,a_k<=a_i^i - 1f_k + clac(k+1,i) \]
\(calc(k + 1,i - 1)\)就是把\([k + 1,i - 1]\)按照顺序填上若干个\(a_k\),再填上若干个\(a_i\)

\(O(n)\)扫一下

所以这dp的时间复杂度为\(n^3\)

考虑如何优化

我们发现,转移和当前位的值息息相关,枚举就是为了寻找满足条件的解

我们索性把这个东西存到状态里

\(f_i,j\)表示\(b_i\)的大小为\(j\)时的方案数

由于\(a_i\)的值过大,我们离散化即可

转移就有
\[ f_i,j = \min_k = 1^jf_i - 1,k + |a_i - j| \]
就是枚举上一个填了什么

我们发现这个DP的时间复杂度仍然是\(n^3\)

但是,这个DP和上一道题有一个共同之处

\(k\)增加\(1\)决策最多增加一个

我们仍然可以通过维护最优决策的方式完成\(O(1)\)转移

那为什么第一个DP不好优化呢?因为第一个DP的公共决策不规律,而且转移的时间不好降

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 4005;
const LL INF = 1e15;
int a[N];
int b[N];
LL f[N][N];
int n; 
LL ans = INF;
inline int read()
    int v = 0,c = 1;char ch = getchar();
    while(!isdigit(ch))
        if(ch == '-') c = -1;
        ch = getchar();
    
    while(isdigit(ch))
        v = v * 10 + ch - 48;
        ch = getchar();
    
    return v * c;

inline void pre()
    for(int i = 1;i <= n;++i)
        for(int j = 1;j <= n;++j) f[i][j] = INF;

int main()
    n = read();
    for(int i = 1;i <= n;++i) b[i] = a[i] = read();
    sort(b + 1,b + n + 1);
    b[0] = unique(b + 1,b + n + 1) - b - 1;
    for(int i = 1;i <= n;++i) a[i] = lower_bound(b + 1,b + b[0] + 1,a[i]) - b;
    pre();
    for(int i = 1;i <= n;++i)
        LL v = INF;
        for(int j = 1;j <= b[0];++j)
            v = min(v,f[i - 1][j]);
            f[i][j] = v + abs(b[j] - b[a[i]]);
        
    
    for(int i = 1;i <= b[0];++i) ans = min(ans,f[n][i]);
    pre();
    for(int i = 1;i <= n;++i)
        LL v = INF;
        for(int j = b[0];j >= 1;--j)
            v = min(v,f[i - 1][j]);
            f[i][j] = v + abs(b[j] - b[a[i]]);
        
     
    for(int i = 1;i <= b[0];++i) ans = min(ans,f[n][i]);
    printf("%lld\n",ans);
    return 0;

TYVJ1071

技术图片

首先有一个比较直接的DP方式

\(f_i,x,y,z\)表示

完成了前\(i\)个请求,三个服务员分别在\(x,y,z\)的位置的最优答案(我们这里强制\(x <y<z\))

转移就有(这里\(x,y,p_i\)都是无序的,实际实现时自行排序)
\[ f_i + 1,x,y,p_i = \min(f_i + 1,x,y,p_i + 1,f_i,x,y,z + c_z,p_i + 1)\f_i + 1,x,y,p_i = \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 = \min(f_i + 1,p_i + 1,y,z,f_i,x,y,z + c_x,p_i + 1) \]
我们发现,处理完第\(i\)个请求,一定会有一个人在\(p_i\)所以我们记录的三个人的状态,其中有一个人是多余的

我们就设\(f_i,j,k\)表示处理完第\(i\)个请求,不在\(p_i\)上的两个人分别在\(x,y\)上是的最优答案

转移同上讨论即可

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 1005;
const int M = 205;
int f[2][M][M];
int c[M][M];
int n,m,now = 0;
int a[N];
inline int read()
    int v = 0,c = 1;char ch = getchar();
    while(!isdigit(ch))
        if(ch == '-') c = -1;
        ch = getchar();
    
    while(isdigit(ch))
        v = v * 10 + ch - 48;
        ch = getchar();
    
    return v * c;

int main()
    m = read(),n = read();
    for(int i = 1;i <= m;++i)
        for(int j = 1;j <= m;++j) c[i][j] = read();
    for(int i = 1;i <= n;++i) a[i] = read();
    a[0] = 3;
    memset(f,0x3f,sizeof(f));
    f[0][1][2] = 0;
    for(int i = 0;i < n;++i)
        memset(f[now ^ 1],0x3f,sizeof(f[now ^ 1]));
        for(int j = 1;j <= m;++j)
            for(int k = 1;k <= m;++k)
                if(j == k) continue;
                if(j == a[i] || k == a[i]) continue;
                f[now ^ 1][j][k] = min(f[now ^ 1][j][k],f[now][j][k] + c[a[i]][a[i + 1]]);
                int x = min(a[i],k),y = max(a[i],k);
                f[now ^ 1][x][y] = min(f[now ^ 1][x][y],f[now][j][k] + c[j][a[i + 1]]);
                x = min(a[i],j),y = max(a[i],j);
                f[now ^ 1][x][y] = min(f[now ^ 1][x][y],f[now][j][k] + c[k][a[i + 1]]);
            
           
        now ^= 1;
    
    int ans = 2e9;
    for(int l = 1;l <= m;++l)
        for(int r = l + 1;r <= m;++r) ans = min(ans,f[now][l][r]);  
    printf("%d\n",ans);
    return 0;

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

牛客剑指offer刷题记录

LeetCode刷题--点滴记录019

每周刷题记录--by noble_

动态规划刷题记录1(不定期更新~)

LeetCode刷题--点滴记录023

LeetCode刷题--点滴记录022