背包
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 int
inline 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 int
inline 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 110
char 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 int
using 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 int
const 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 int
const 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;
}