采药(01背包
luogu P1048
背包动规模板题
01背包问题
LL dp[maxn],w[maxn],t[maxn];
LL tt,m;
int main(){
tt=lrd();
m=lrd();
for(int i=1;i<=m;i++){
t[i]=lrd();
w[i]=lrd();
}
for(int i=1;i<=m;i++){
for(int j=tt;j>=t[i];j--)
dp[j]=max(dp[j],dp[j-t[i]]+w[i]);
}
cout<<dp[tt]<<endl;
return 0;
}
疯狂的采药(完全背包
完全背包模板
LL dp[maxn],w[maxn],t[maxn];
LL tt,m;
int main(){
tt=lrd();
m=lrd();
for(int i=1;i<=m;i++){
t[i]=lrd();
w[i]=lrd();
}
for(int i=1;i<=m;i++){
for(int j=t[i];j<=tt;j++)//01背包区别
dp[j]=max(dp[j],dp[j-t[i]]+w[i]);
}
cout<<dp[tt]<<endl;
return 0;
}
金明的预算方案(依赖背包
四层dp状态转移,依赖背包
第0层表示所含附件的个数(主件为1),第一层代表主件,以后为附件1、2。购买附件的代价需要预处理加上主件的代价。
附件的购买情况分为三种:
- 只购买附件1
- 只购买附件2
- 附件1与附件二一起购买
LL dp[maxn],w[maxn][10],v[maxn][10];
LL n,m;
int main(){
n=lrd();
m=lrd();
for(int i=1;i<=m;i++){
LL a,b,c;
a=lrd();
b=lrd();
c=lrd();
if(c==0){//主件
w[i][1]=a;
v[i][1]=b*a;
}
else{//附件
w[c][++w[c][0]+1]=a;
v[c][++v[c][0]+1]=a*b;
if(w[c][0]==2){
w[c][w[c][0]+1]=w[c][2]+w[c][3];
v[c][v[c][0]+1]=v[c][2]+v[c][3];
}
}
}
for(int i=1;i<=m;i++){//预处理
if(w[i][0]!=0){
w[i][2]+=w[i][1];
v[i][2]+=v[i][1];
if(w[i][0]==2){
w[i][3]+=w[i][1];
v[i][3]+=v[i][1];
w[i][4]+=w[i][1];
v[i][4]+=v[i][1];
}
}
}
for(int i=1;i<=m;i++){
for(int j=n;j>=w[i][1];j--){
dp[j]=max(dp[j],dp[j-w[i][1]]+v[i][1]);
if(j>=w[i][2])
dp[j]=max(dp[j],dp[j-w[i][2]]+v[i][2]);
if(j>=w[i][3])
dp[j]=max(dp[j],dp[j-w[i][3]]+v[i][3]);
if(j>=w[i][4])
dp[j]=max(dp[j],dp[j-w[i][4]]+v[i][4]);
}
}
cout<<dp[n]<<endl;
return 0;
}
数的划分 P1025
忘记了就去看题解,下一个
int dp[220][10],n,k;
int main(){
n=ird();
k=ird();
memset(dp,0,sizeof dp);
for(int i=1;i<=n;i++){
dp[i][1]=1;
}
for(int i=2;i<=n;i++){
for(int j=2;j<=k;j++){
if(j>i)
dp[i][j]=0;
else
dp[i][j]=dp[i-1][j-1]+dp[i-j][j];
}
}
cout<<dp[n][k]<<endl;
}
多米诺骨牌
背包问题拓展
- 转换为背包,首次反转贡献为-1
LL a[maxn][5],w[maxn],v[maxn],dp[maxn*6][maxn],vis[maxn*6][maxn];
LL n,m,ans=0;
int main(){
n=lrd();
m=0;
for(int i=1;i<=n;i++){
a[i][1]=lrd();
a[i][2]=lrd();
if(a[i][1]<a[i][2]){
ans++;
swap(a[i][1],a[i][2]);
v[i]=-1;
}
else if(a[i][1]==a[i][2])
v[i]=0;
else
v[i]=1;
w[i]=a[i][1]-a[i][2];
m+=w[i];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
dp[j][i]=dp[j][i-1];
vis[j][i]=vis[j][i-1];
if(j>=2*w[i]){
if(vis[j-2*w[i]][i-1]||j==2*w[i]){
if(vis[j][i]==0){
dp[j][i]=dp[j-2*w[i]][i-1]+v[i];
vis[j][i]=1;
}
else
dp[j][i]=min(dp[j][i],dp[j-2*w[i]][i-1]+v[i]);
}
}
}
}
int f;
for(f=m;f>=1;f--){
if(vis[f][n]==1)
break;
}
cout<<ans+dp[f][n]<<endl;
}
石子合并(模板
luogu P1880
模板题
dp[i][j]表示第i到第j堆的合并
输入从1-n*2实现一圈循环,dp时固定i,遍历ij之间中间划分块。
最后ans枚举起始点i从1-n,所有石子都要合成一堆
int a[maxn],sum[maxn];
int dpi[maxn][maxn],dpa[maxn][maxn];
int main(){
int n=ird();
for(int i=1;i<=n*2;i++){
if(i<=n) a[i]=ird();
a[i+n]=a[i];
sum[i]=sum[i-1]+a[i];
}
for(int d=1;d<n;d++){
for(int i=1,j=i+d;j<n*2&&i<n*2;i++,j=i+d){
dpi[i][j]=1e9;
for(int k=i;k<j;k++){
dpi[i][j]=min(dpi[i][j],dpi[i][k]+dpi[k+1][j]+sum[j]-sum[i-1]);
dpa[i][j]=max(dpa[i][j],dpa[i][k]+dpa[k+1][j]+sum[j]-sum[i-1]);
}
}
}
int ans=0,anss=1e8;
for(int i=1;i<=n;i++){
anss=min(anss,dpi[i][i+n-1]);
ans=max(ans,dpa[i][i+n-1]);
}
cout<<anss<<endl<<ans<<endl;
return 0;
}
能量项链
同石子合并
int a[maxn],dp[maxn][maxn];
struct node{
int h,t;
}mp[maxn];
int n,m;
int main(){
n=ird();
for(int i=1;i<=n;i++){
a[i]=ird();
}
for(int i=1;i<=n;i++){
mp[i].h=a[i];
mp[i+n].h=a[i];
if(i==n){
mp[i].t=a[1];
}
else mp[i].t=a[i+1];
mp[i+n].t=mp[i].t;
}
for(int d=1;d<n;d++){
for(int i=1,j=i+d;i<=n*2&&j<=n*2;i++,j=i+d){
for(int k=i;k<j;k++){
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+mp[i].h*mp[k].t*mp[j].t);
}
}
}
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,dp[i][i+n-1]);
}
cout<<ans<<endl;
return 0;
}
最大正方形(线性dp 1
dp[i][j]代表(i,j)为右下角的正方形的最大边长
- 转移方程:
-
dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1**
-
int mp[maxn][maxn],dp[maxn][maxn];
int n,m;
int main(){
n=ird();
m=ird();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
mp[i][j]=ird();
if(mp[i][j])
dp[i][j]=1;
}
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(mp[i][j]) dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
ans=max(ans,dp[i][j]);
}
}
cout<<ans<<endl;
return 0;
}
创意吃鱼法 (线性dp2
luogu P1736
和上一题类似,多了一个预处理
- 处理以(i,j)为起点,向上、左、右三个方向扩展连续的0的最大长度
- mz数组记录左右拓展最大长度(前缀和),ms数组记录向上最大连续0的长度。
- 因为对角线有俩,第一遍统计向左的。mz记录👈最大值。第二遍统计向右的,mz记录👉最大值 (or MLE)
- 每次遍历ans比较记录最大值。
int mp[maxn][maxn],mz[maxn][maxn],ms[maxn][maxn],dp[maxn][maxn];
int n,m;
int main(){
n=ird();
m=ird();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
mp[i][j]=ird();
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(!mp[i][j])
mz[i][j]=mz[i][j-1]+1;
}
}
for(int j=1;j<=m;j++){
for(int i=1;i<=n;i++){
if(!mp[i][j])
ms[i][j]=ms[i-1][j]+1;
}
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(mp[i][j]){
if(dp[i-1][j-1]>=1){
dp[i][j]=min(min(mz[i][j-1],ms[i-1][j]),dp[i-1][j-1])+1;
}
else
dp[i][j]=1;
ans=max(ans,dp[i][j]);
}
}
}
memset(dp,0,sizeof dp);
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
if(!mp[i][j])
mz[i][j]=mz[i][j+1]+1;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(mp[i][j]){
if(dp[i-1][j+1]>=1){
dp[i][j]=min(min(mz[i][j+1],ms[i-1][j]),dp[i-1][j+1])+1;
}
else
dp[i][j]=1;
ans=max(ans,dp[i][j]);
}
}
}
cout<<ans<<endl;
return 0;
}