DP动态规划练习
先来看一下经典的背包问题吧
http://www.cnblogs.com/Kalix/p/7617856.html 01背包问题
https://www.cnblogs.com/Kalix/p/7622102.html 完全背包问题
https://blog.csdn.net/mystery_guest/article/details/51878140 多重背包二进制优化
1.https://cn.vjudge.net/problem/12304/origin POJ 3176
从上往下走或者右下走找最大总和,也可以不同dp写。
注意动态规划这一步一定是和上一步或下一步有关联的
//dp[i][j]表示走到i,j这步时的最大值
for(i=1;i<=n;i++) { for(j=1;j<=i;j++) { dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+map[i][j];//(i,j)这一步是从(i-1,j)或者(i-1,j-1)走过来的 ans=max(ans,dp[i][j]); } }
全部代码
#include<stdio.h> #include<algorithm> #include<iostream> using namespace std; int n,map[1000][1000],dp[1000][1000]; int ans=0; int main() { cin>>n; for(int i=1;i<=n;i++) { for(int j=1;j<=i;j++) { cin>>map[i][j]; } } int i,j; for(i=1;i<=n;i++) { for(j=1;j<=i;j++) { dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+map[i][j]; ans=max(ans,dp[i][j]); } } cout<<ans; }
2.https://cn.vjudge.net/problem/30465/origin POJ 2229
这题是看一个数能用几个不同二次方幂的数和表示
首先来练习一下深搜把,这一开始我没想到还能用深搜,尽管不对,当然可慢啦啦啦啦
#include<stdio.h> #include<math.h> #include<iostream> using namespace std; int ans; long long p[50]; void dfs(int n,int last) { if(n==0) { ans++; return; } for(int i=0;n-p[i]>=0;i++) { if(p[i]>=last) dfs(n-p[i],p[i]); } } int main() { int n; for(int i=0;i<=49;i++) p[i]=pow(2,i); cin>>n; dfs(n,0); printf("%d",ans); return 0; }
接下来是dp版的,不知道为啥,还是超时!!!!,但思想要学习下,看懂下面的那个dp更新表就欧克了
dp表假设只有1,然后添加2,然后添加4然后添加8,更新表
#include<iostream> #include<string.h> #include<stdio.h> using namespace std; long long dp[1000001],p[30]; int main() { int n; while(scanf("%d",&n)!=EOF) { memset(dp,0,sizeof(dp)); p[0]=dp[0]=1; for(int i=1;i<=29;i++) { p[i]=p[i-1]<<1; } for(int i=0;i<=29;i++) { if(p[i]<=n) { for(int j=p[i];j<=n;j++) { dp[j]=(dp[j]+dp[j-p[i]])%1000000000; } } } /*dp 1 2 3 4 5 6 7//假设输入7 1 1 1 1 1 1 1 1 2 2 2 3 3 4 4 4 4 4 6 6 */ cout<<dp[n]<<endl; } }
最后过的是这个,我他喵。。。
#include<iostream> #include<string.h> using namespace std; long long a[1000001]; int main() { int n; a[1]=1,a[2]=2; for(int i=3;i<1000001;i++) { a[i]=a[i-2]+a[i/2]; a[i]%=1000000000; } cin>>n; cout<<a[n]; }
1.n为奇数,a[n]=a[n-1]
2.n为偶数:
(1)如果加数里含1,则一定至少有两个1,即对n-2的每一个加数式后面 +1+1,总类数为a[n-2];
(2)如果加数里没有1,即对n/2的每一个加数式乘以2,总类数为a[n/2];
3.https://cn.vjudge.net/problem/18264/origin POJ 2385
题意有两棵树1,2,掉苹果,人一开始在1下,有指定的移动次数,给苹果下落,求最多接到的苹果数
首先想一想,跟平常的题有什么区别
1.人会移动,
2.苹果要判断是否要不移动接到,还是移动接到
3.移动的次数不像是背包问题里的背包容量越多越好,但和背包问题类似
for(int i=1;i<=n;i++) { dp[i][0]=dp[i-1][0];//第i个苹果,j次移动的最好结果,判断一直不动的情况 if(t[i]==1) dp[i][0]++; for(int j=1;j<=w;j++) { if(j%2+1==t[i]){//判断移动后的位置在哪,如果跟这次下落苹果的位置一样 dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+1;//dp的关键,跟上次的东西有关联,接这个苹果我可以选择i-1个苹果移动相同的次数,然后不动接
//也可以从另一个苹果树移动过来接 } else dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]); } }
整体代码
#include<iostream> #include<string.h> #include<algorithm> using namespace std; int t[1000]; int dp[1000][1000]; int main() { int n,w; cin>>n>>w; for(int i=1;i<=n;i++) { cin>>t[i]; } memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) { dp[i][0]=dp[i-1][0]; if(t[i]==1) dp[i][0]++; for(int j=1;j<=w;j++) { if(j%2+1==t[i]){ dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+1; } else dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]); } } cout<<dp[n][w]<<endl; }
4.https://cn.vjudge.net/problem/16276/origin POJ 3616
给定时间段,每个时间段有工作效率,每个时间段都要休息,求最大工作量
1.首先想到的是要对这些时间段排序,根据结束时间
2.对他们进行动态规划,具体思想可以参考求一个序列的最大上升子序列
#include<iostream> #include<string.h> #include<algorithm> using namespace std; struct node{ int s,e,ef; }x[100000]; bool cmp(node a,node b){ return a.e<b.e; } int dp[100000]; int main() { int a,b,c; cin>>a>>b>>c; for(int i=1;i<=b;i++) { cin>>x[i].s>>x[i].e>>x[i].ef; x[i].e+=c; } sort(x+1,x+1+b,cmp); memset(dp,0,sizeof(dp)); int ans=0; for(int i=1;i<=b;i++) { dp[i]=x[i].ef; for(int j=1;j<i;j++) { if(x[i].s>=x[j].e) { dp[i]=max(dp[i],dp[j]+x[i].ef);//判断选这个或者不选这个,根据工作量大小 } } ans=max(ans,dp[i]); } cout<<ans; }
牛客多校第二场a题
一个人可以走一步或者跳x步,但不能连着跳,问到这个区间里有几种走法
考虑两种状态 对于这一点,我可以走过来,前面是怎么样的我不用管,也可以跳过来但是,跳过来必须保证前一步是走的
dp[i][0]表示i这一步是走过来的dp[i][1]表示i这一步是跳过来的
#include<iostream> #include<bits/stdc++.h> using namespace std; long long dp[100002][2]; long long mod=1e9+7; long long ans[100002]; int main() { int n,w; cin>>n>>w; dp[0][0]=1; for(int i=1;i<=100002;i++) { dp[i][0]=(dp[i-1][0]+dp[i-1][1])%mod; if(i>=w) dp[i][1]=dp[i-w][0]%mod; } for(int i=1;i<=100002;i++) { ans[i]=(dp[i][0]+dp[i][1]+ans[i-1])%mod; } //for(int i=1;i<=10;i++) //cout<<ans[i]<<endl; while(n--) { long long l,r; cin>>l>>r; cout<<(ans[r]-ans[l-1]+mod)%mod<<endl; } return 0; }
[POJ-3280]
给一串字符串,添加字母或者去掉字母使其变成回文串,不过有代价 ,让代价最小
#include<iostream> #include<string.h> #include<stdio.h> #include<algorithm> using namespace std; int n,m,dp[2020][2020],t[2010],k,p; int main() { char s[100],a[2020]; while(cin>>n>>m) { memset(dp,0,sizeof(dp)); cin>>a; for(int i=0;i<n;i++) { cin>>s>>k>>p; t[s[0]-'a']=min(k,p); } for(int i=1;i<m;i++) { for(int j=i-1;j>=0;j--) { dp[j][i]=min(dp[j+1][i]+t[a[j]-'a'],dp[j][i-1]+t[a[i]-'a']); if(a[i]==a[j]) dp[j][i]=min(dp[j+1][i-1],dp[j][i]); } } cout<<dp[0][m-1]<<endl; } }
:Tony想要买一个东西,他只有n中硬币每种硬币的面值为a[i]每种硬币的数量为c[i]要买的物品价值不超过m
多重背包的二进制优化
#include <iostream> #include <cstring> #define INF 0x3f3f3f3f using namespace std; int f[111111],a[111],c[111]; int n,m; //m背包的总容量、v物品的体积、w物品的价值 void OneZeroPack(int m,int v,int w) //0-1背包 { for(int i=m;i>=v;i--) f[i]=max(f[i],f[i-v]+w); } //m背包的总容量、v物品的体积、w物品的价值 void CompletePack(int m,int v,int w) //完全背包 { for(int i=v;i<=m;i++) f[i]=max(f[i],f[i-v]+w); } //m背包的总容量、v物品的体积、w物品的价值、num物品的数量 void MultiplePack(int m,int v,int w,int num)//多重背包 { if(v*num>=m) { CompletePack(m,v,w); return ; } int k=1; for(k=1;k<=num;k<<=1) { OneZeroPack(m,k*v,k*w); num=num-k; } if(num) OneZeroPack(m,num*v,num*w); } int main() { while(cin>>n>>m) { if(n==0&&m==0) break; for(int i=0;i<n;i++) cin>>a[i]; for(int i=0;i<n;i++) cin>>c[i]; for(int i=0;i<=m;i++) f[i]=-INF; f[0]=0; for(int i=0;i<n;i++) { MultiplePack(m,a[i],a[i],c[i]); } int sum=0; for(int i=1;i<=m;i++) if(f[i]>0) sum++; cout<<sum<<endl; } return 0; }
hdu 2089
http://acm.hdu.edu.cn/showproblem.php?pid=2089
算是数位dp的入门吧
求n到m之间数字中没有4和62(连着)的个数
#include <algorithm> #include <iostream> using namespace std; int dp[10][10]; void init(){ dp[0][0] = 1;//dp[0][1-9] = 1都可以 for(int i = 1; i <= 8; i++) for(int j = 0; j < 10; j++)//i位 for(int k = 0; k < 10; k++)//i-1位 if(j != 4 && !(j == 6 && k == 2)) dp[i][j] += dp[i-1][k]; } int solve(int n){ int d[10]; int len = 0; while(n > 0){//数组保存 d[++len] = n%10; n /= 10; } d[len + 1] = 0; int ans = 0; for(int i = len; i >= 1; i--){ for(int j = 0; j < d[i]; j++){ if(j != 4 && !(d[i+1] == 6 && j == 2)) ans += dp[i][j];//求和 } if(d[i] == 4 || (d[i] == 2 && d[i+1] == 6)) break;//判出 } return ans; } int main() { int r, l; init(); while(cin>>l>>r){ if(r + l == 0) break; cout<<solve(r+1) - solve(l)<<endl; } return 0; }
题目大意:蚂蚁牙黑,蚂蚁牙红:有A只蚂蚁,来自T个家族,分别记为ant[i]个。同一个家族的蚂蚁长得一样,但是不同家族的蚂蚁牙齿颜色不同。任取n只蚂蚁(S <= n <= B),求能组成几种集合?
状态:dp[i][j]:前i种中选j个可以组成的种数
决策:第i种选k个,k<=ant[i] && j-k>=0
转移:dp[i][j]=Σdp[i-1][j-k]
#include<stdio.h> #include<string.h> int dp[1005][10050]; int num[10050]; int main(){ int n,m,a,b,x; while(scanf("%d%d%d%d",&n,&m,&a,&b)!=EOF){ memset(num,0,sizeof(num)); for(int i=0;i<m;i++){ scanf("%d",&x); num[x]++; } memset(dp,0,sizeof(dp)); for(int i=0;i<=num[1];i++) dp[1][i]=1; for(int i=2;i<=n;i++){ for(int j=0;j<=b;j++){ for(int k=0;k<=num[i];k++){ if(j>=k) { dp[i][j]+=dp[i-1][j-k]; dp[i][j]%=1000000; } } } } int ans=0; for(int i=a;i<=b;i++){ ans+=dp[n][i]; ans%=1000000; } printf("%d\n",ans); } return 0; }
还有一种
dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-ant[i]-1]
#include<iostream> using namespace std; #define MOD 1000000 int T, A, S, B; int ant[1005]; int dp[2][100000]; int ans; int main() { scanf("%d%d%d%d", &T, &A, &S, &B); for (int i = 1; i <= A; i++) { int aa; scanf("%d", &aa); ant[aa]++; } dp[0][0] = dp[1][0] = 1; for (int i = 1; i <= T; i++) for (int j = 1; j <= B; j++) if (j - ant[i] - 1 >= 0) dp[i % 2][j] = (dp[(i - 1) % 2][j] + dp[i % 2][j - 1] - dp[(i - 1) % 2][j - ant[i] - 1] + MOD) % MOD; //在取模时若出现了减法运算则需要先+Mod再对Mod取模,防止出现负数(如5%4-3%4为负数) else dp[i % 2][j] = (dp[(i - 1) % 2][j] + dp[i % 2][j - 1]) % MOD; for (int i = S; i <= B; i++) ans = (ans + dp[T % 2][i]) % MOD; printf("%d\n", ans); return 0; }