【笔记】动态规划w
来源:互联网 发布:ubuntu trusty 源 编辑:程序博客网 时间:2024/05/17 03:59
- 背包
- 01
- 完全
- 多重
- 混合
- 划分型
- 树型DP
- 棋盘型DP
- 其他dp
背包
http://blog.csdn.net/lyhvoyage/article/details/8545852
(个人认为这个不知名大佬写的不错,再就是去看看背包九讲的前几讲emmmmm)
01
有N件物品和一个容量为V的背包。(每种物品均只有一件)第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大
eg:http://codevs.cn/problem/5709/
#include<bits/stdc++.h>using namespace std;const int sz = 100010;const int inf = 214748364;typedef long long LL;#define ri register intinline void rd(int &x){ x=0;bool f=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-') f=1;c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} if(f) x*=-1;}int a[sz],v,n,ans;int w[sz],k[sz],dp[1010][1010];int main(){ rd(v),rd(n); for(ri i=1;i<=n;++i) rd(w[i]),rd(k[i]); for(ri i=1;i<=n;++i) for(ri j=1;j<=v;j++) if(j>=w[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+k[i]); else dp[i][j]=dp[i-1][j]; cout<<dp[n][v]; return 0;}
这是二维转移,对于第一维我们可以优化掉,优化空间
#include<bits/stdc++.h>using namespace std;int F[1001];int c[1001],v[1001]; int main(){ int s,n; cin>>s>>n; for(int k=1;k<=n;k++) cin>>c[k]>>v[k]; for(int i=1;i<=n;i++) for(int j=s;j>=c[i];j--)//那个物体可以放进去 F[j]=max(F[j],F[j-c[i]]+v[i]); cout<<F[s];//询问能否将一个容量s的背包填到最大价值 return 0;}
完全
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大
同样有二维和一维写法
/*完全背包 每一个物品都有无限个状态转移方程,d[i][j]=max(dp[i-1][j],dp[i-1][j-k*c[i]]+k*w[i]),if(j-k*c[i]>=0)边界 dp[0][j]=0 j>=0&&j<=v尽量填充背包,不要求装满 dp[0][0]=0 dp[0][j]=-INF j>=1&&j<=v 恰好装满*/#include<iostream>#include<cstdio>#include<cstring>using namespace std;const int V=1000,N=100;int dp[N][V],c[N],w[N];int main(){ memset(dp,0,sizeof(dp)); int v,n; cin>>n>>v; for(int i=1;i<=n;i++) cin>>c[i]>>w[i]; for(int i=1;i<=n;i++) { for(int j=1;j<=v;j++) dp[i][j]=dp[i-1][j]; for(int j=c[i];j<=v;j++) for(int k=1;k*c[i]<=j;k++) dp[i][j]=max(dp[i][j],dp[i-1][j-k*c[i]]+k*w[i]); } cout<<dp[n][v]<<" "; return 0;}
一维写法:
#include<bits/stdc++.h>using namespace std;const int V=1000,N=100;int f[N],c[N],w[N];int main(){ int v,n; cin>>v>>n; for(int i=1;i<=n;i++) cin>>c[i]>>w[i]; for(int i=1;i<=n;++i) for(int j=c[i];j<=v;++j) f[j]=max(f[j],f[j-c[i]]+w[i]); cout<<f[v]; return 0;}
多重
多重背包问题要求简单,就是每件物品给出确定的件数,求可得到的最大价值
多重背包转换成 01 背包问题就是多了个初始化,把它的件数C 用二进制分解成若干个件数的集合,这里面数字可以组合成任意小于等于C的件数,而且不会重复,之所以叫二进制分解,是因为这样分解可以用数字的二进制形式来解释
比如:7的二进制 7 = 111 它可以分解成 001 010 100 这三个数可以组合成任意小于等于7 的数,而且每种组合都会得到不同的数
拆分出来不全为1的数,比如13 = 1101 则分解为 0001 0010 0100 0110,前三个数字可以组合成 7以内任意一个数,即1、2、4可以组合为1——7内所有的数,加上 0110 = 6 可以组合成任意一个大于6 小于等于13的数,比如12,可以让前面贡献6且后面也贡献6就行了。虽然有重复但总是能把 13 以内所有的数都考虑到了,基于这种思想去把多件物品转换为,多种一件物品,就可用01 背包求解
二进制分解+01
#include<bits/stdc++.h>using namespace std;const int sz = 1010; int T,V,n,i,j,k,v[sz],w[sz],c[sz],dp[sz];//v[]存价值,w[]存尺寸,c[]存件数 本题中价值是米重量,尺寸是米价格 int main(){int sum,Value[sz],size[sz];//sum存储分解完后的物品总数//Value存储分解完后每件物品的价值//size存储分解完后每件物品的大小 cin>>T;while(T--){ sum=0; cin>>V>>n; for(i=0;i<n;i++) { cin>>w[i]>>v[i]>>c[i]; //对该种类的c[i]件物品进行二进制分解 for(j=1;j<=c[i];j<<=1)//相当于x2 { Value[sum]=j*v[i]; size[++sum]=j*w[i]; c[i]-=j; } if(c[i]>0) { Value[sum]=c[i]*v[i]; size[++sum]=c[i]*w[i]; } } //经过上面对每一种物品的分解, //现在Value[]存的就是分解后的物品价值 //size[]存的就是分解后的物品尺寸 //sum就相当于原来的n 下面就直接用01背包算法来解 memset(dp,0,sizeof(dp)); for(i=0;i<sum;i++) for(j=V;j>=size[i];j--) if(dp[j]<dp[j-size[i]]+Value[i]) dp[j]=dp[j-size[i]]+Value[i]; cout<<dp[V]<<endl;}return 0;}
两种包一起
#include<bits/stdc++.h>using namespace std;const int sz = 100010;const int inf = 214748364;typedef long long LL;#define ri register intinline void rd(int &x){ x=0;bool f=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-') f=1;c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} if(f) x*=-1;}int n,m,dp[1010],t;int p[1010],h[1010],c[1010];void wan(int v,int w){ for(int i=v;i<=n;i++) if(dp[i]<dp[i-v]+w) dp[i]=dp[i-v]+w; }void ling(int v,int w){ for(ri i=n;i>=v;--i) if(dp[i]<dp[i-v]+w) dp[i]=dp[i-v]+w;}int main(){rd(t);while(t--){ memset(dp,0,sizeof(dp)); rd(n),rd(m); for(ri i=1;i<=m;++i) { rd(p[i]),rd(h[i]),rd(c[i]); if(p[i]*c[i]>=n) wan(p[i],h[i]); else { for(ri j=1;j<c[i];j<<1) { ling(j*p[i],j*h[i]); c[i]=c[i]-j; } } } printf("%d\n",dp[n]);} return 0;}
混合
背包体积为V ,给出N个物品,每个物品占用体积为Vi,价值为Wi,每个物品要么至多取1件,要么至多取mi件(mi > 1) , 要么数量无限 , 在所装物品总体积不超过V的前提下所装物品的价值的和的最大值是多少?
#include<iostream>#include<cstdio>using namespace std;int main(){ int v,n; while(scanf("%d%d",&v,&n)!=EOF) { int w[1000],c[1000],p[1000],i,k=0,t,j,f[202]={0}; for(i=1;i<=n;i++) { t=1; scanf("%d%d%d",&w[i],&c[i],&p[i]); if(p[i]>1) //将有限个相同价格的物品转化为不同价格的单个物品 { while(p[i]>t) { k++; w[n+k]=w[i]*t; c[n+k]=c[i]*t; p[n+k]=1; p[i]-=t; t*=2; } w[i]*=p[i]; c[i]*=p[i]; p[i]=1; } } for(i=1;i<=n+k;i++) if(p[i]==1) //判断是01背包还是完全背包 for(j=v;j>=w[i];j--) f[j]=f[j]>f[j-w[i]]+c[i] ? f[j]:f[j-w[i]]+c[i]; else for(j=w[i];j<=v;j++) f[j]=f[j]>f[j-w[i]]+c[i] ? f[j]:f[j-w[i]]+c[i]; printf("%d\n",f[v]); } return 0;}
划分型
目前涉及到的划分型dp不多,算上数的划分这个题目的话也是寥寥几个,不知道总结的对不对,总之先把自己的想法写出来
eg:乘积最大 http://codevs.cn/problem/1017/
划分型dp和区间型dp区分上目前还未细致研究,但是听说挺相似的2333
对于划分dp应该是有明显的划分条件,将什么什么划分到哪里or在一个连续的阶段中插入分层处理。
个人觉得有时候这个分析很不好分析,对于这种问题我们要结合连续阶段分析划分的情况怎么处理,有时候我们会选择三层for循环来枚举断点,在断点处进行选择插入断点新情况和不选择插入的老情况的比较,进行答案的统计和更新
#include<bits/stdc++.h>using namespace std;#define sz 110char x[1010];int a[sz][sz],f[sz][sz],n,m;int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%c",&x[i]),a[i][i]=x[i]-'0'; for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) a[i][j]=a[i][j-1]*10+x[j]-'0';//i位->j位的数字 for(int i=1;i<=n;i++) f[i][0]=a[1][i];//初始化 for(int k=1;k<=m;k++)//循环分割次数 for(int i=k+1;i<=n;i++)//分割k次至少需要k+1位数字 for(int j=k;j<i;j++)//循环分割位置 f[i][k]=max(f[i][k],f[j][k-1]*a[j+1][i]);//前面*后面的数字 printf("%d\n",f[n][m]); return 0;}
树型DP
eg:
二维:没有上司的舞会
三维:愚蠢的矿工
树型DP的套路一般是处理树结构上的问题,当然区间和序列也可以转化成树来处理。树型DP出现指数级枚举组合数时,采用左儿子右兄弟法来处理
#include<iostream>#include<cstdio>#include<cstring>#define RI register intusing namespace std;const int sz = 100000;int fir[sz],nxt[sz],dp[sz][2]; int root,tot = 1,n,ru[sz];bool use[sz];struct ed{ int t,d;}l[sz];inline void build(int f,int t){ l[tot].t = t; nxt[tot] = fir[f]; fir[f] = tot ++;}void dfs(int p,int f){ use[p] = 1; for(RI i = fir[p] ; i ; i = nxt[i]) { int t = l[i].t; if(t != f && use[t]!=1) { use[t] = 1; dfs(t,p); dp[p][0] = max(dp[p][0],dp[t][1]+dp[p][0]); dp[p][1] = max(dp[p][1],max(dp[t][1],dp[t][0])+dp[p][1]); } }}int f,t;int main(){ cin>>n; for(RI i = 1 ; i <= n ; i ++) scanf("%d",&dp[i][0]); for(RI i = 1 ; i < n ; i ++) { scanf("%d%d",&f,&t); ru[f] ++; build(f,t); build(t,f); } scanf("%d%d",&root,&root); for(RI i = 1 ; i <= n ; i ++) if(ru[i] == 0) { root = i; break; } dfs(root,-1); cout<<max(dp[root][0],dp[root][1])<<'\n'; return 0;}
棋盘型DP
eg:传纸条
前两个和后两个分别代表了两条不同的路径的状态,通过不同的转移(主要是那一长串max)来完成答案的统计
#include <bits/stdc++.h>using namespace std;typedef long long LL;#define RI register intconst int sz = 100010;const int inf = 1e8;int m,n,x,f[60][60][60][60],a[55][55];int main(){ cin>>m>>n; for(RI i=1;i<=m;++i) for(RI j=1;j<=n;++j) { cin>>x; a[i][j]=x; }for(RI i=1;i<=m;++i)for(RI j=1;j<=n;++j)for(RI k=1;k<=m;++k)for(RI l=1;l<=n;++l){ f[i][j][k][l]=max( f[i-1][j][k-1][l], max(f[i][j-1][k-1][l],max(f[i-1][j][k][l-1],f[i][j-1][k][l-1]) ) )+a[i][j]+a[k][l]; if(i==k&&j==l) f[i][j][k][l]-=a[i][j];} printf("%d",f[m][n][m][n]); return 0;}
还有像是乌龟棋这样的棋盘型的核心
f[0][0][0][0]=step[1];for(int i=0;i<=s1;i++)for(int j=0;j<=s2;j++)for(int k=0;k<=s3;k++)for(int l=0;l<=s4;l++){ now=i+j*2+k*3+l*4+1; if(i>0) f[i][j][k][l]= max(f[i][j][k][l],f[i-1][j][k][l]+step[now]); if(j>0) f[i][j][k][l]= max(f[i][j][k][l],f[i][j-1][k][l]+step[now]); if(k>0) f[i][j][k][l]= max(f[i][j][k][l],f[i][j][k-1][l]+step[now]); if(l>0) f[i][j][k][l]= max(f[i][j][k][l],f[i][j][k][l-1]+step[now]); }printf("%d\n",f[s1][s2][s3][s4]);
其他dp?
其实感觉这个更像是递推什么的emmmm
但是不加滚动数组的这个只能处理1w左右的数据,否则会TLE
eg:矿工配餐
五维DP只要不是很丧病,一般就是第一维枚举位置状态,第二三维表示第一个情况的选择状态,第四五维表示第二个情况的选择状态,最终用总的状态求解
多用于求相同的两者的符合题意状态之和的最大或最小值
eg:以使得两个煤矿的产煤量的总和最大
代码:
#include <bits/stdc++.h>using namespace std;typedef long long LL;#define ri register intconst int sz = 1000010;inline void read(int &x){ x=0;bool fl=0;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') fl=1;c=getchar();} while(c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();} if(fl) x*=-1;}inline int check(int a,int b,int c){ if(a==0&&b==0) return 1; if(a==0) return 1+(b!=c);//代表如果b!=c返回1 if(a==b&&b==c) return 1; if(a==b||b==c||a==c) return 2; return 3;}int t[sz],n,f[sz][4][4][4][4];char s[sz];int dp(int pos,int a,int b,int x,int y){ if(pos==n+1) return 0; if(f[pos][a][b][x][y]!=-1) return f[pos][a][b][x][y]; return f[pos][a][b][x][y]=max(dp(pos+1,b,t[pos],x,y)+check(a,b,t[pos]),dp(pos+1,a,b,y,t[pos])+check(x,y,t[pos]));}int main(){ read(n); scanf("%s",s); for(ri i=0;i<n;++i)//!从0开始读 if( s[i] == 'B' ) t[i+1] = 1; else if( s[i] == 'M' ) t[i+1] = 2; else t[i + 1] = 3; memset(f,-1,sizeof(f)); cout<<dp(1,0,0,0,0); return 0;}
- 【笔记】动态规划w
- DP 动态规划 Problem W 1023 背包
- 动态规划学习笔记
- 动态规划学习笔记
- 动态规划学习笔记
- 动态规划笔记
- 动态规划算法笔记
- 动态规划--学习笔记
- ACM 动态规划笔记
- 【算法】动态规划笔记
- 动态规划学习笔记
- 动态规划笔记
- 动态规划-入门笔记
- 学习笔记---动态规划
- 动态规划简要笔记
- 动态规划学习笔记
- 动态规划学习笔记
- 动态规划笔记
- android 蓝牙操作
- js一些常用的功能的应用
- 微服务设计笔记
- tableexports.js 微解析
- 网络数据修改工具netsed
- 【笔记】动态规划w
- HTTPS流量激增,2/3Google用户访问的网页启用加密
- 抽象接口和抽象类
- 购物车
- tomcat 调试模式 启动
- Angularjs学生信息管理
- CPU密集型和IO密集型的理解
- 简单的实现WaveSwipeRefreshLayout的下拉刷新(嵌套RecyclerView)
- Tomcat基础