背包问题有许多
01背包:一种只能取一次
多重背包:一种可以取有限的次数
完全背包:一种可以取无数次
啧
最近做dp做的快吐了···
先来个简单的01背包
luogu1060 自己找题吧···
直接上代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,v[30],w[30],dp[26][30000],ans;
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>v[i]>>w[i];
}
for(int i=1;i<=m;i++)
for(int j=i;j<=n;j++)
{
if(j-v[i]>=0)
dp[i][j]=max(dp[i-1][j-v[i]]+w[i]*v[i],dp[i-1][j]);//dp方程
else dp[i][j]=dp[i-1][j];
}
for(int i=1;i<=m;i++)
{
for(int j=i;j<=n;j++)
ans=max(ans,dp[i][j]);
}
cout<<ans<<endl;
return 0;
}
luogu1164
求方案数的
也是01背包
#include<iostream>
#include<cstdio>
#define maxn 10005
using namespace std;
int n,m,a[maxn],dp[maxn],mx;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
dp[0]=1;
for(int i=1;i<=n;i++)
for(int j=m;j>=a[i];j--)//注意这里要倒着推,才能保证不会重复算
{
dp[j]=dp[j-a[i]]+dp[j];
}
cout<<dp[m]<<endl;
return 0;
}
然后再来个完全背包的吧
luogu1616
#include<iostream>
#include<cstdio>
using namespace std;
int t,m,v[10005],tim[10005],dp[100005];
int main()
{
cin>>t>>m;
for(int i=1;i<=m;i++)
{
cin>>tim[i]>>v[i];
}
for(int i=1;i<=t;i++)//以时间为第一重循环
{
int maxn=0;
for(int j=1;j<=m;j++)
{
if(i-tim[j]>=0)
maxn=max(maxn,dp[i-tim[j]]+v[j]);
else maxn=max(maxn,dp[i]);//dp方程
}
dp[i]=maxn;//算出每个时间能达到的最大值
}
cout<<dp[t]<<endl;
return 0;
}
然后多重背包
luogu1077 数据范围不大暴力枚举就行
多重背包好像可以二进制拆分优化
但这道求方案数的好像不太可以
粘代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define maxn 105
#define md 1000007
using namespace std;
int n,m,a[maxn],dp[maxn][maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=0;i<=m;i++) dp[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=j;k>=j-a[i];k--)
{
if(k>=0)
{
dp[i][j]+=dp[i-1][k],dp[i][j]%=md;
}
else break;
}
printf("%d\n",dp[n][m]);
return 0;
}
咳咳
这么多水题
来个难的
luogu1880石子合并
令人窒息的操作
其实就是线性dp
遇到环的问题先复制转化成线
然后倒推起点
一段一段搜
取各个段中间的值都遍历一遍
找最大值和最小值
其他的线性dp和这个思路差不多都
#include<iostream>
#include<cstdio>
#define INF 0x7fffff
using namespace std;
int n,a[205],b[205],mx,mn,dps[2005][2005],dpl[2005][2005];
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],a[i+n]=a[i];//复制
for(int i=1;i<=2*n;i++) b[i]=b[i-1]+a[i];//前缀和
for(int i=2*n-1;i>=1;i--)//倒推起点
for(int j=i+1;j<=i+n-1;j++)//长度为n的一段
{
dps[i][j]=INF;
for(int k=i;k<j;k++)//遍历中间值
{
dps[i][j]=min(dps[i][j],dps[i][k]+dps[k+1][j]+b[j]-b[i-1]);//dp方程
dpl[i][j]=max(dpl[i][j],dpl[i][k]+dpl[k+1][j]+b[j]-b[i-1]);
}
}
mn=INF;
for(int i=1;i<=n;i++)
{
mx=max(mx,dpl[i][i+n-1]);
mn=min(mn,dps[i][i+n-1]);
}
cout<<mn<<endl<<mx<<endl;
return 0;
}
以及luogu1057
也是一个环的问题
不过这个问题有些不同
思路就是这个人的等于左边传过来的加上右边传过来的
注意第一个和最后一个要单独拿出来
#include<iostream>
#include<cstdio>
using namespace std;
int n,m,dp[35][35];
int main()
{
cin>>n>>m;
dp[1][0]=1;
for(int i=1;i<=m;i++)
{
dp[1][i]=dp[2][i-1]+dp[n][i-1];
dp[n][i]=dp[n-1][i-1]+dp[1][i-1];
for(int j=2;j<n;j++)
dp[j][i]=dp[j-1][i-1]+dp[j+1][i-1];
}
cout<<dp[1][m]<<endl;
return 0;
}
luogu1063
依然是环的问题
和石子合并一个思路
也是一段段的推
在这提供两种写法
第一种:
以i为一段的长度,j为起点,i+j-1为终点,k为中间值
第二种:
以i为起点,j为终点,每段都是n,k为中间值
两种思想都一样
但是还是认为第二种思路更清晰
第一种比较迷容易写错
#include<iostream>
#include<cstdio>
#define maxn 300
using namespace std;
int n,a[maxn],dp[maxn][maxn],mx;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
a[i+n]=a[i];
}
for(int i=2;i<=n+1;i++)
for(int j=1;j<=2*n && i+j-1<=2*n ;j++)
for(int k=j+1;k<i+j-1;k++)
{
int r=i+j-1;
dp[j][r]=max(dp[j][r],dp[j][k]+dp[k][r]+a[j]*a[k]*a[r]);
mx=max(mx,dp[j][r]);
}//第一种
for(int i=2*n-1;i;i--)
for(int j=i+1;j<i+n && j<=2*n;j++)
for(int k=j-1;k>=i;k--)//这里正反无所谓
{
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[i]*a[k+1]*a[j+1]);
mx=max(dp[i][j]);
}//第二种
cout<<mx<<endl;
return 0;
}