背包问题:
首先我们考虑暴力法:每种情况试一下看看最小。这样的每层递归要分两次,为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 }
然后我们再来想:画它的递归树会发现有很多重复的不需要第二次计算的东西
所以我们开一个数组来储存它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 }
顺序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 }
简单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 }
让我们来这样考虑状态转移:
我们会想:
从前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 }
通过重用一个数组来降低空间复杂度
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 }
完全背包问题:和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 }
通过重用一个数组来减少空间复杂度
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 }
滚动数组来实现数组的重复利用:
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 }
多重部分和问题:
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 }
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 }
最长上升子序列问题:
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 }
优化:感觉这个只能用来求长度不能拿来输出,因为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 }
学习了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 }
多重集组合数:公式的变换能很大程度上降低复杂度
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 }
熟练掌握动态规划:
旅行商问题
最短路径
铺砖问题
矩阵的幂/幂和
给方块刷漆
借助数据结构高效求解。
今天先看到这,看后把题目做完,之后回来吧熟练掌握和总结。然后把greedy搞懂,再就是回溯,最大流,割点。,