zoukankan      html  css  js  c++  java
  • 挑战程序设计竞赛 dp

    背包问题:

    首先我们考虑暴力法:每种情况试一下看看最小。这样的每层递归要分两次,为O(2^n)

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 using namespace std;
     5 #define maxn 105
     6 int n,W;
     7 int w[maxn],v[maxn];
     8 
     9 int rec(int i,int j)
    10 {
    11     int res;
    12     if(i==n) res=0;
    13     else if(j<w[i]) res=rec(i+1,j);
    14     else res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);
    15     return res;
    16 }
    17 
    18 int main()
    19 {
    20     scanf("%d",&n);
    21     for(int i=0;i<n;i++)
    22         scanf("%d%d",&w[i],&v[i]);
    23     scanf("%d",&W);
    24     printf("%d
    ",rec(0,W));
    25 }
    View Code

    然后我们再来想:画它的递归树会发现有很多重复的不需要第二次计算的东西

    所以我们开一个数组来储存它dp[maxn+1][maxn+1]当他已经存在就不需要计算他了。记忆化存储的思想-》推出来状态转移方程。

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 using namespace std;
     5 #define maxn 105
     6 int n,W;
     7 int w[maxn],v[maxn];
     8 int dp[maxn+1][maxn+1];
     9 
    10 int rec(int i,int j)
    11 {
    12     int res;
    13     if(i==n) res=0;
    14     else if(j<w[i]) res=rec(i+1,j);
    15     else res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);
    16     return res;
    17 }
    18 
    19 int main()
    20 {
    21     scanf("%d",&n);
    22     for(int i=0;i<n;i++)
    23         scanf("%d%d",&w[i],&v[i]);
    24     scanf("%d",&W);
    25     memset(dp,-1,sizeof(dp));
    26     printf("%d
    ",rec(0,W));
    27 }
    View Code

    顺序dp

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 using namespace std;
     5 #define maxn 105
     6 int n,W;
     7 int w[maxn],v[maxn];
     8 int dp[maxn+1][maxn+1];
     9 
    10 void solve()
    11 {
    12     for(int i=0;i<n;i++)
    13         for(int j=0;j<=W;j++)
    14         {
    15             if(j<w[i])
    16                 dp[i+1][j]=dp[i][j];
    17             else
    18                 dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
    19         }
    20 }
    21 
    22 int main()
    23 {
    24     scanf("%d",&n);
    25     for(int i=0;i<n;i++)
    26         scanf("%d%d",&w[i],&v[i]);
    27     scanf("%d",&W);
    28     memset(dp,0,sizeof(dp));
    29     solve();
    30     printf("%d
    ",dp[n][W]);
    31 }
    View Code

    简单dp数组,根据记忆化数组的推导,等熟悉之后也可以直接推导出下面的状态转移方程

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 using namespace std;
     5 #define maxn 105
     6 int n,W;
     7 int w[maxn],v[maxn];
     8 int dp[maxn+1][maxn+1];
     9 
    10 void solve()
    11 {
    12     for(int i=n-1;i>=0;i--)
    13         for(int j=0;j<=W;j++)
    14         {
    15             if(j<w[i])
    16                 dp[i][j]=dp[i+1][j];
    17             else
    18                 dp[i][j]=max(dp[i+1][j],dp[i+1][j-w[i]]+v[i]);
    19         }
    20 }
    21 
    22 int main()
    23 {
    24     scanf("%d",&n);
    25     for(int i=0;i<n;i++)
    26         scanf("%d%d",&w[i],&v[i]);
    27     scanf("%d",&W);
    28     memset(dp,0,sizeof(dp));
    29     solve();
    30     printf("%d
    ",dp[0][W]);
    31 }
    View Code

    让我们来这样考虑状态转移:

    我们会想:

    从前i个物品中选出不超过j的状态向从前i+1个物品中选出不超过j的状态和前i+1个物品中选出不超过j+w[i]物品的状态

    优化的dp数组:

    状态转移方程:

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 using namespace std;
     5 #define maxn 105
     6 int n,W;
     7 int w[maxn],v[maxn];
     8 int dp[maxn+1];
     9 
    10 int dynamicp(int i,int j)
    11 {
    12     for(int k=0;k<n;k++)
    13         for(int m=j;m>=w[k];m--)
    14             dp[m]=max(dp[m],dp[m-w[k]]+v[k]);
    15     return dp[j];
    16 }
    17 
    18 int main()
    19 {
    20     scanf("%d",&n);
    21     for(int i=0;i<n;i++)
    22         scanf("%d%d",&w[i],&v[i]);
    23     scanf("%d",&W);
    24     memset(dp,0,sizeof(dp));
    25     printf("%d
    ",dynamicp(0,W));
    26 }
    View Code

    通过重用一个数组来降低空间复杂度

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 using namespace std;
     5 #define maxn 105
     6 int n,W;
     7 int w[maxn],v[maxn];
     8 int dp[maxn+1];
     9 
    10 void solve()
    11 {
    12     for(int i=0;i<n;i++)
    13         for(int j=W;j>=w[i];j--)
    14         {
    15             dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    16         }
    17 }
    18 
    19 int main()
    20 {
    21     scanf("%d",&n);
    22     for(int i=0;i<n;i++)
    23         scanf("%d%d",&w[i],&v[i]);
    24     scanf("%d",&W);
    25     memset(dp,0,sizeof(dp));
    26     solve();
    27     printf("%d
    ",dp[W]);
    28 }
    View Code

    完全背包问题:和01背包的j的取值反过来了

    做了一个代换让原来的n*w^2的公式变成了n*w

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 using namespace std;
     5 #define maxn 105
     6 int n,W;
     7 int w[maxn],v[maxn];
     8 int dp[maxn+1][maxn+1];
     9 
    10 void solve()
    11 {
    12     for(int i=0;i<n;i++)
    13         for(int j=0;j<=W;j++)
    14         {
    15             if(j<w[i])
    16                 dp[i+1][j]=dp[i][j];
    17             else
    18                 dp[i+1][j]=max(dp[i][j],dp[i+1][j-w[i]]+v[i]);
    19         }
    20 }
    21 
    22 int main()
    23 {
    24     scanf("%d",&n);
    25     for(int i=0;i<n;i++)
    26         scanf("%d%d",&w[i],&v[i]);
    27     scanf("%d",&W);
    28     memset(dp,0,sizeof(dp));
    29     solve();
    30     printf("%d
    ",dp[n][W]);
    31 }
    View Code

    通过重用一个数组来减少空间复杂度

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 using namespace std;
     5 #define maxn 105
     6 int n,W;
     7 int w[maxn],v[maxn];
     8 int dp[maxn+1];
     9 
    10 void solve()
    11 {
    12     for(int i=0;i<n;i++)
    13         for(int j=w[i];j<=W;j++)
    14         {
    15             dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    16         }
    17 }
    18 
    19 int main()
    20 {
    21     scanf("%d",&n);
    22     for(int i=0;i<n;i++)
    23         scanf("%d%d",&w[i],&v[i]);
    24     scanf("%d",&W);
    25     memset(dp,0,sizeof(dp));
    26     solve();
    27     printf("%d
    ",dp[W]);
    28 }
    View Code

    滚动数组来实现数组的重复利用:

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 using namespace std;
     5 #define maxn 105
     6 int n,W;
     7 int w[maxn],v[maxn];
     8 int dp[2][maxn+1];
     9 
    10 void solve()
    11 {
    12     for(int i=0;i<n;i++)
    13         for(int j=0;j<=W;j++)
    14         {
    15             if(j<w[i])
    16                 dp[(i+1)&1][j]=dp[i&1][j];
    17             else
    18                 dp[(i+1)&1][j]=max(dp[i&1][j],dp[(i+1)&1][j-w[i]]+v[i]);
    19         }
    20 }
    21 
    22 int main()
    23 {
    24     scanf("%d",&n);
    25     for(int i=0;i<n;i++)
    26         scanf("%d%d",&w[i],&v[i]);
    27     scanf("%d",&W);
    28     memset(dp,0,sizeof(dp));
    29     solve();
    30     printf("%d
    ",dp[n&1][W]);
    31 }
    View Code

    多重部分和问题:

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 #include <cstring>
     5 #define inf 0x3f3f3f3f
     6 using namespace std;
     7 #define maxn 105
     8 #define maxk 100005
     9 
    10 
    11 int n,K;
    12 int a[maxn],m[maxn];
    13 int dp[maxn+1][maxk+1];
    14 
    15 void solve()
    16 {
    17     for(int i=0;i<n;i++)
    18         for(int j=0;j<=K;j++)
    19         {
    20             for(int k=0;k<=m[i] && k*a[i]<=j;k++)
    21                 dp[i+1][j]|=dp[i][j-k*a[i]];
    22         }
    23     if(dp[n][K]) printf("YES
    ");
    24     else printf("No
    ");
    25 }
    26 
    27 int main()
    28 {
    29     scanf("%d",&n);
    30     for(int i=0;i<n;i++)
    31         scanf("%d%d",&a[i],&m[i]);
    32     scanf("%d",&K);
    33     memset(dp,-1,sizeof(dp));
    34     solve();
    35     return 0;
    36 }
    View Code
     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 #include <cstring>
     5 
     6 using namespace std;
     7 #define maxn 105
     8 #define maxk 100005
     9 
    10 
    11 int n,K;
    12 int a[maxn],m[maxn];
    13 int dp[maxk+1];
    14 
    15 void solve()
    16 {
    17     for(int i=0;i<n;i++)
    18         for(int j=0;j<=K;j++)
    19         {
    20             if(dp[j]>=0)
    21                 dp[j]=m[i];
    22             else if(j<a[i] || dp[j-a[i]]<=0)
    23                 dp[j]=-1;
    24             else
    25                 dp[j]=dp[j-a[i]]-1;
    26         }
    27     if(dp[K]) printf("YES
    ");
    28     else printf("No
    ");
    29 }
    30 
    31 int main()
    32 {
    33     scanf("%d",&n);
    34     for(int i=0;i<n;i++)
    35         scanf("%d%d",&a[i],&m[i]);
    36     scanf("%d",&K);
    37     dp[0]=0;
    38     solve();
    39     return 0;
    40 }
    View Code

    最长上升子序列问题:

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 
     6 using namespace std;
     7 #define maxn 1005
     8 int dp[maxn+1];
     9 
    10 int main()
    11 {
    12     int n;
    13     int a[maxn];
    14     scanf("%d",&n);
    15     for(int i=0;i<n;i++)
    16         scanf("%d",&a[i]);
    17     
    18     int res=0;
    19     for(int i=0;i<n;i++)
    20     {
    21         dp[i]=1;
    22         for(int j=0;j<i;j++)
    23         {
    24             if(a[j]<a[i])
    25                 dp[i]=max(dp[i],dp[j]+1);
    26         }
    27         res=max(res,dp[i]);
    28     }
    29     printf("%d
    ",res);
    30     return 0;
    31 }
    View Code

    优化:感觉这个只能用来求长度不能拿来输出,因为dp数组的每个值是上升子序列的最小值,也就是说不是严格意义上在数组中按照顺序的上升子序列。

    不过对于单纯优化了时间复杂度的情况下来看还是有意义的。万一我一个数值庞大的问题只需要求它的上升子序列的长度呢= =

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 #define inf 0x3f3f3f3f
     6 using namespace std;
     7 #define maxn 1005
     8 int dp[maxn+1];
     9 
    10 int main()
    11 {
    12     int n;
    13     int a[maxn];
    14     scanf("%d",&n);
    15     for(int i=0;i<n;i++)
    16         scanf("%d",&a[i]);
    17     fill(dp,dp+n,inf);
    18     for(int i=0;i<n;i++)
    19         *(lower_bound(dp,dp+n,a[i]))=a[i];
    20     printf("%d
    ",lower_bound(dp,dp+n,inf)-dp);
    21     return 0;
    22 }
    View Code

    学习了lower_bound 和upper_bound的思想很重要

    一个数组中找有多少个k,upper_bound(a,a+n,k)-lower_bound(a,a+n,k)就可以了  O(logn)

    有关计数问题的dp:

    划分数:注意两点:1.   1  2 1 和 1 1 2属于相同的划分方法,因为数是无序的且相同,所以这样子装入箱子的状态是相同的,

    2.一种划分装入一个箱子的数量可以为0

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 using namespace std;
     6 #define maxn 1005
     7 int dp[maxn+1][maxn+1];
     8 
     9 int main()
    10 {
    11     int n,m,M;
    12     scanf("%d%d%d",&n,&m,&M);
    13     dp[0][0]=1;
    14     for(int i=1;i<=m;i++)
    15         for(int j=0;j<=n;j++)
    16         {
    17             if(j-i>=0)
    18                 dp[i][j]=(dp[i-1][j]+dp[i][j-i])%M;
    19             else
    20                 dp[i][j]=dp[i-1][j];
    21         }
    22     printf("%d
    ",dp[m][n]);
    23     return 0;
    24 }
    View Code

    多重集组合数:公式的变换能很大程度上降低复杂度

    j-1-a[i]在出现负数的时候是不允许的,所以所else就直接删除掉了。

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 using namespace std;
     6 #define maxn 1005
     7 int dp[maxn+1][maxn+1];
     8 
     9 int main()
    10 {
    11     int n,m,M;
    12     scanf("%d%d%d",&n,&m,&M);
    13     int a[maxn];
    14     for(int i=0;i<n;i++)
    15         scanf("%d",&a[i]);
    16     for(int i=0;i<=n;i++)
    17         dp[i][0]=1;
    18     for(int i=0;i<n;i++)
    19         for(int j=1;j<=m;j++)
    20         {
    21             if(j-1-a[i]>=0)
    22                 dp[i+1][j]=(dp[i+1][j-1]+dp[i][j]-dp[i][j-1-a[i]]+M)%M;
    23             else
    24                 dp[i+1][j]=(dp[i+1][j-1]+dp[i][j])%M;
    25         }
    26     printf("%d
    ",dp[m][n]);
    27     return 0;
    28 }
    View Code

    熟练掌握动态规划:

    旅行商问题

    最短路径

    铺砖问题

    矩阵的幂/幂和

    给方块刷漆

    借助数据结构高效求解。

     今天先看到这,看后把题目做完,之后回来吧熟练掌握和总结。然后把greedy搞懂,再就是回溯,最大流,割点。,

    活在现实,做在梦里。
  • 相关阅读:
    BZOJ2763 [JLOI2011]飞行路线(SPFA + DP)
    HDU5838 Mountain(状压DP + 容斥原理)
    HDU4787 GRE Words Revenge(AC自动机 分块 合并)
    HDU5909 Tree Cutting(树形DP + FWT)
    HDU5456 Matches Puzzle Game(DP)
    SPOJ DQUERY D-query(主席树)
    POJ2104 K-th Number(主席树)
    Tsinsen A1493 城市规划(DP + CDQ分治 + NTT)
    BZOJ3438 小M的作物(最小割)
    BZOJ1565 [NOI2009]植物大战僵尸(拓扑排序 + 最大权闭合子图)
  • 原文地址:https://www.cnblogs.com/do-it-best/p/5465513.html
Copyright © 2011-2022 走看看