• 动态规划问题基础


    关于dp问题的一些汇总( ´▽`)

    一、线型dp

    就是普通的dp啊..

    最大上升子序列最长公共子序列(LCS)都用到了这种思想w

    二、背包型dp

    具体参考背包九讲吧qwq

    常用的大概就01背包,完全背包,多重背包,有依赖的背包( Luogu P1064 金明的预算方案)...

    01背包和完全背包的区别就在于空间的枚举顺序,倒序枚举可以防止重复拿同一个物品。

    //01背包
    for(int i = 1;i <= n;i++)
        for(int j = m;j >= 1;j--)
            f[j] = max(f[j],f[j-c[i]]+w[i]); 
            
    //完全背包
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
            f[j] = max(f[j],f[j-c[i]]+w[i]); 

    (话说金明这道题我用的是枚举每个空间然后tle了....看到题解是枚举四种情况emmm....)

    #include<cstdio>
    #include<iostream>
    using namespace std;
    
    const int maxn = 32000,subm = 100;
    int sum[subm][maxn];
    int head[subm],to[subm],next[subm],c[subm],val[subm];
    int n,m,v,p,q,cnt;
    
    int add(int i,int v,int p,int q) {
        c[i] = v;
        val[i] = v*p;
        to[++cnt] = i;
        next[cnt] = head[q];
        head[q] = cnt;
    }
    
    void dfs(int x,int fa) {
        sum[x][c[x]] = val[x];
        for(int i = head[x]; i; i = next[i]) {
            int t = to[i];
            dfs(t,x);
            for(int j = n-c[fa]; j >= c[x]+c[t]; j--)
                for(int k = j-c[x]; k >= c[t]; k--)
                    sum[x][j] = max(sum[x][j],sum[x][j-k]+sum[t][k]);
        }
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= m; i++) {
            scanf("%d%d%d",&v,&p,&q);
            add(i,v,p,q);
        }
        dfs(0,-1);
        printf("%d",sum[0][n]);
        return 0;
    }
      金明的错误代码QAQ   

    但是我觉得这种思想是比较有普遍性的!可以适用于一般的树形dp↓

    、树型dp

    就是由下到上在树上dp;

    Luogu P2014 选课P2015 二叉苹果树都是比较经典的树形背包,循环为父亲空间儿子空间;

    状态转移方程:

     for(int i = m; i > 1; i--)  //m为总容量
                for(int j =i-1; j >= 1; j--)
                    sum[u][i] = max(sum[u][i],sum[u][i-j]+sum[v][j]);
    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    
    const int maxn = 305;
    int n,m,cnt;
    int a,b;
    int sum[maxn][maxn];
    int head[maxn],to[maxn],next[maxn],val[maxn];
    
    void add(int x,int y,int z) {
        to[++cnt] = y;
        next[cnt] = head[x];
        head[x] = cnt;
        val[y] = z; 
    }
    
    void dfs(int u,int fa) {
        for(int i = head[u]; i; i = next[i]) {
            int v = to[i];
            if(v == fa)continue;
            sum[v][1] = val[v];
            dfs(v,u);
            for(int j = m+1; j > 1; j--)
                for(int k =j-1; k >= 1; k--)
                    sum[u][j] = max(sum[u][j],sum[u][j-k]+sum[v][k]);
        }
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= n; i++) {
            scanf("%d%d",&a,&b);
            add(a,i,b);
        }
        dfs(0,0);
        printf("%d ",sum[0][m+1]);
        return 0;
    }
      选课  

    Luogu P1352 没有上司的舞会P2016 战略游戏都是选父亲不选儿子的(应该说是比较简单的类型...)

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    
    using namespace std;
    const int maxn = 2005;
    int n,k,u,v,cnt;
    int f[maxn],g[maxn];
    int head[maxn],to[maxn],nxt[maxn];
    
    void add(int x,int y){
        to[++cnt] = y;
        nxt[cnt] = head[x];
        head[x] = cnt;
    }
    
    void dfs(int x){
        for(int i = head[x];i;i = nxt[i]){
            int t = to[i];
            dfs(t);
            f[x] += min(f[t],g[t]);
            g[x] += f[t];
        }
    }
    
    int main() {
        scanf("%d",&n);
        for(int i = 1; i <= n; i++) {
            scanf("%d%d",&u,&k);
            for(int j = 1; j <= k; j++) {
                scanf("%d",&v);
                add(u,v);
            }
        }
        for(int i = 0;i < n;i++){
            f[i] = 1;
            g[i] = 0;
        }
        dfs(0);
        printf("%d",min(f[0],g[0]));
        return 0;
    }
      战略游戏  

    Luogu P2458 保安站岗就更复杂一点了...一个节点可以眺望到相邻的节点,这时就需要讨论自己/父亲/儿子三种情况,

    难点是,因为一个节点会对它的兄弟造成影响,显然不是无后效性的操作,所以在讨论选儿子的情况时需要把所有儿子都枚举一遍后,

    选择由不选变为选的状态,增加量最小的儿子。

    #include<cstdio>
    #include<iostream>
    #define MogeKo qwq
    using namespace std;
    
    const int maxn = 2005*2;
    const int INF = 2147483647;
    int n,k,u,v,cnt;
    int f[maxn][3],head[maxn],to[maxn],nxt[maxn];
    
    void add(int x,int y) {
        to[++cnt] = y;
        nxt[cnt] = head[x];
        head[x] = cnt;
    }
    
    void dfs(int x,int fa) {
        int d = INF;
        for(int i = head[x]; i; i = nxt[i]) {
            int t = to[i];
            if(t == fa)continue;
            dfs(t,x);
            f[x][2] += min(min(f[t][0],f[t][1]),f[t][2]);
            f[x][0] += min(f[t][1],f[t][2]);
            f[x][1] += min(f[t][1],f[t][2]);
            d = min(d,f[t][2]-min(f[t][1],f[t][2]));
        }
        f[x][1]+=d;
    }
    
    int main() {
        scanf("%d",&n);
        for(int i = 1; i <= n; i++) {
            scanf("%d",&u);
            scanf("%d%d",&f[u][2],&k);
            for(int j = 1; j<= k; j++) {
                scanf("%d",&v);
                add(u,v);
                add(v,u);
            }
        }
        dfs(1,0);
        printf("%d",min(f[1][2],f[1][1]));
        return 0;
    }
      保安站岗  

    四、区间型dp

    区间型dp一般适用于相邻的对象每次操作都会影响结果的问题,主要思想是枚举每个小区间和断点,最后合并成大区间。

    一般是n^3的复杂度,循环顺序是长度→起点(终点)→断点;

    状态转移方程:

    for(int len=2; len<=n; len++) //区间长度
        for(int i=1; i<=n; i++) {    //枚举起点
            int j = i+len-1;           //区间终点
            if(j>n) break;           //判断越界
            for(int k=i; k<j; k++)  //枚举分割点
                dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]); 
        }

    Luogu P1063 能量项链P1880 [NOI1995]石子合并都是区间dp的板子题...

    石子合并需要注意:因为每次合并是要加上两部分的总和,所以这时用前缀和就非常方便w

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn = 305,INF = 2147483647;
    int n,ans1,ans2,a[maxn],f[maxn][maxn],g[maxn][maxn];
    int main(){
        scanf("%d",&n);
        for(int i = 1;i <= n;i++){
            scanf("%d",&a[i]);
            a[i+n] = a[i];
        }
        for(int i = 1;i <= 2*n;i++)
            a[i] += a[i-1];
        for(int l = 1;l < n;l++)
            for(int i = 1;i+l <= 2*n;i++){
                int j = i+l;
                f[i][j] = 0;
                g[i][j] = INF;
                for(int k = i;k < j;k++){
                    f[i][j] = max(f[i][j],f[i][k]+f[k+1][j]+a[j]-a[i-1]);
                    g[i][j] = min(g[i][j],g[i][k]+g[k+1][j]+a[j]-a[i-1]);
                }
            }
        ans1 = 0;
        ans2 = INF;
        for(int i = 1;i <= n;i++){
            ans1 = max(ans1,f[i][i+n-1]);
            ans2 = min(ans2,g[i][i+n-1]);
        }
        printf("%d
    %d",ans2,ans1);
        return 0;
    }
      石子合并  

    五、坐标型dp

    坐标型dp适用于知道每个物品与不同的选择的对应关系,并且选择需要是单调不能回头的。

    形象地说,就像在一张表格中走出一条权值最大的路。

    随便走一条路:

     那么这条路径是如何确定的呢?

     假设走到了第二列,需要给B选择。可选的区间为2~8,因为至少要给前面的A留一个,后面的C、D留两个,即为(int j = i; v-j >= u-i; j++)

    比如枚举到(B,6),此时要从它的祖先(即A行)中比它列数小的(1~5)中选择最优解。

    状态转移方程:

     for(int i = 1; i <= u; i++)    //枚举行数(A~D物品) 
            for(int j = i; v-j >= u-i; j++)        //枚举列数 
                for(int k = i-1; k <= j-1; k++)    //枚举祖先 
                    if(f[i][j] < f[i-1][k]+a[i][j]) 
                        f[i][j] = f[i-1][k]+a[i][j];

    Luogu P1854 花店橱窗布置P1006 传纸条都是比较经典的例题,

    花店橱窗布置需要输出路径,所以要开一个last数组,在枚举祖先的同时记录;

    传纸条是一来一回且路径不能重复,所以可以同时枚举两条路径的坐标。因为是同步移动的,所以知道了i1、j1、i2就能算出j2,开三重循环就可以了。

    #include<cstdio>
    #define MogeKo qwq
    using namespace std;
    
    const int maxn = 105;
    const int INF = 2147483647;
    int u,v,rslt;
    int a[maxn][maxn],f[maxn][maxn],last[maxn][maxn],ans[maxn];
    
    int main() {
        scanf("%d%d",&u,&v);
        for(int i = 1; i <= u; i++)
            for(int j = 1; j <= v; j++) {
                scanf("%d",&a[i][j]);
                f[i][j] = -INF;
            }
        for(int j = 1; j <= v; j++)
            f[1][j] = a[1][j];
        for(int i = 1; i <= u; i++)
            for(int j = i; v-j >= u-i; j++)
                for(int k = i-1; k <= j-1; k++)
                    if(f[i][j] < f[i-1][k]+a[i][j]) {
                        f[i][j] = f[i-1][k]+a[i][j];
                        last[i][j] = k;
                    }
        rslt = -INF;
        int k;
        for(int j = u; j <= v; j++)
            if(rslt < f[u][j]) {
                rslt = f[u][j];
                k = j;
            }
        for(int i = u; i >= 1; i--) {
            ans[i] = k;
            k = last[i][k];
        }
        printf("%d
    ",rslt);
        for(int i = 1; i <= u; i++)
            printf("%d ",ans[i]);
        return 0;
    }
      花店橱窗布置  
    #include<cstdio>
    #include<iostream>
    #define MogeKo qwq
    using namespace std;
    
    const int maxn = 105;
    int n,m,ans,a[maxn][maxn],f[maxn][maxn][maxn];
    
    int main(){
        scanf("%d%d",&m,&n);
        for(int i = 1;i <= m;i++)
            for(int j = 1;j <= n;j++)
                scanf("%d",&a[i][j]);
        for(int i1 = 1;i1 <= m;i1++)
            for(int j1 = 1;j1 <= n;j1++)
                for(int i2 = 1;i2 <= m;i2++){
                    int j2 = i1+j1-i2;
                    if(i1 == i2 && j1 == j2 && (i1!=1 || j1!=1))continue;
                    f[i1][j1][i2] = max(max(f[i1-1][j1][i2-1],f[i1][j1-1][i2-1]),max(f[i1-1][j1][i2],f[i1][j1-1][i2]));
                    f[i1][j1][i2]+=(a[i1][j1]+a[i2][j2]);
                    ans = max(ans,f[i1][j1][i2]);
                }
        printf("%d",ans);
        return 0;
    }
      传纸条  

    我个人感觉坐标dp是刚开始学比较难理解的一个地方qwq

    六、单调队列优化dp

    Luogu P1725琪露诺

    因为题解咕了太久我都忘了( ´▽`)

    #include<cstdio>
    #include<iostream>
    #define MogeKo qwq
    using namespace std;
    
    const int maxn = 1000005;
    int n,l,r,a[maxn],ans;
    int q[maxn],num[maxn],head,tail;
    
    int main() {
        scanf("%d%d%d",&n,&l,&r);
        for(int i = 0; i <= n; i++)
            scanf("%d",&a[i]);
        head = 1,tail = 0;
        for(int i = l; i <= n; i++) {
            while(head <= tail && q[i-l] >= q[num[tail]])tail--;
            num[++tail] = i-l;
            while(num[head] < i-r)head++;
            q[i] = q[num[head]]+a[i];
            if(i>=n-r+1)ans = max(ans,q[i]);
        }
        printf("%d",ans);
        return 0;
    }
      琪露诺  
  • 相关阅读:
    (转)linux下控制帐户过期的多种方法
    跟老男孩学Linx运维---web集群实战笔记
    (转)企业生产环境用户权限集中管理方案案例
    Linux 运维培训笔记
    (转)sudo配置文件/etc/sudoers详解及实战用法
    (转) RHEL7 忘记密码修改root密码
    (转)Mysql数据库之Binlog日志使用总结CentOS 7.x设置自定义开机启动,添加自定义系统服务
    git 删除远程分支
    crontab详解
    PHP数据库长连接mysql_pconnect用法
  • 原文地址:https://www.cnblogs.com/mogeko/p/10354627.html
Copyright © 2020-2023  润新知