挑战程序设计竞赛 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搞懂,再就是回溯,最大流,割点。,

活在现实,做在梦里。
原文地址:https://www.cnblogs.com/do-it-best/p/5465513.html