DP日记01 刷点线性DP
int_Hello_world · · 个人记录
日期:2023.1.11-1.12
前言:
这几天一直刷DP题,记点笔记防止自己摸鱼。
记录一下这几天做的题吧。这一篇记录几道线性dp题
以后再补,咕。
洛谷P1651
传送门
本来我是讲这个题的,就把注释写得挺详细的,不过大家都会了,就没讲,写好的注释没人看也怪可惜的,就放这里了。
思路都在代码里了
代码:
/*
先理题意:
有n块积木,每个积木高度为h[i],每块积木可以选也可以不选
用这些积木去搭两座高度相同的塔,使两座塔高度尽可能高
思路:
两座塔只有高低之分,高的为高塔,低的为低塔
有四种情况:
第一种:第i块积木不用,所以j不发生改变。
第二种:第i块积木放到高塔上,高塔还是高塔。
第三种:第i块积木放到低塔上时,低塔还是低塔
第四种:第i块积木放到低塔上时,低塔变成高塔
*/
#include<bits/stdc++.h>
using namespace std;
int n,m,h[51],dp[51][500001],sum;//h[i]表示第i块积木的高度
//dp[i][j]表示前i块积木,高塔和低塔的高度差为j时所能达到的最高高度
//转移时也会出现4种情况:
//第一种:当前两座塔都没有使用第i块积木,所以直接由上一个状态转移过来
//dp[i][j]=dp[i-1][j]
//第二种:高塔使用了第i块积木,并且在上一个状态中该塔仍然是高塔
//dp[i][j]=dp[i-1][j-h[i]]+h[i]
//第三种:低塔使用了第i块积木,并且在上一个状态中该塔仍然是低塔
//dp[i][j]=dp[i-1][j+h[i]]
//第四种: 现在的高塔使用了第i块积木,但在用第i块积木前,它是低塔
//dp[i][j]=dp[i-1][h[i]-j]+j
//dp[i][j]在上面四种情况里取max即为第i个状态的最优答案
signed main(){
cin>>n;
for (int i=1;i<=n;++i) {
cin>>h[i];
sum+=h[i];//算一下所有积木加起来有多高
}
m=(sum+1)/2;//因为是建两个相同高度的塔,所以最高就是所有积木高度之和的一半
//因为默认向下下去整,所以这里给sum+1防止意外
memset(dp,-0x3f,sizeof(dp));//最终结果取max,dp[0][0]为0,所以其余初始化为负数
dp[0][0]=0;
//初始化
for (int i=1;i<=n;++i) { //第i块积木
for (int j=0;j<=m;++j) { //高的塔与低塔高度之差为j时
dp[i][j]=max(dp[i][j],dp[i-1][j]);//第一种情况
if (h[i]<=j) dp[i][j]=max(dp[i][j],dp[i-1][j-h[i]]+h[i]); //第二种情况
//注意只有当这块积木的高度小于两者高度差时才能转移过来
dp[i][j]=max(dp[i][j],dp[i-1][j+h[i]]); //第三种情况
if(h[i]>=j) dp[i][j]=max(dp[i][j],dp[i-1][h[i]-j]+j); //第四种情况
//只有当这块积木的高度大于两者高度差时才能转移过来
}
}
if (dp[n][0]) {
cout<<dp[n][0];//输出
}
else { //没法搭建出两座相同的塔
putchar('-');
putchar('1');
}
return 0;
}
洛谷P1489
传送门
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,a[10231131],sum[10231311],dp[201][102313];
signed main(){
cin>>n;
for (int i=1;i<=n;++i) {
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
dp[0][0]=1;
for (int i=1;i<=n;++i) {
for (int j=n/2;j>=1;--j) {
for (int k=0;k<=sum[i];++k) {
if (k>=a[i]&&dp[j-1][k-a[i]]==1) {
dp[j][k]=1;
}
}
}
}
for (int i=sum[n]/2;i>=0;--i) {
if (dp[n/2][i]==1) {
cout<<i<<" "<<sum[n]-i;
return 0;
}
}
return 0;
}
CF545C
传送门
#include<bits/stdc++.h>
using namespace std;
int n,m,dp[102311][3];
struct node{
int x,h;
}e[1021311];
bool cmp(node a,node b) {
return a.x<b.x;
}
signed main(){
cin>>n;
for (int i=1;i<=n;++i) {
cin>>e[i].x>>e[i].h;
}
sort(e+1,e+1+n,cmp);
dp[1][0]=1;dp[1][1]=1;
if (e[2].x-e[1].x>e[1].h) dp[1][2]=1;
for (int i=2;i<=n;++i) {
dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
if(e[i].x-e[i-1].x>e[i-1].h) dp[i][0]=max(dp[i][0],dp[i-1][2]);
if(e[i].x-e[i-1].x>e[i].h) dp[i][1]=max(dp[i-1][0],dp[i-1][1])+1;
if (e[i].x-e[i-1].x>e[i].h+e[i-1].h) dp[i][1]=max(dp[i][1],dp[i-1][2]+1);
dp[i][2]=max(dp[i-1][0],dp[i-1][1])+1;
if(e[i].x-e[i-1].x>e[i-1].h) dp[i][2]=max(dp[i][2],dp[i-1][2]+1);
}
cout<<max(dp[n][0],max(dp[n][1],dp[n][2]));
return 0;
}
洛谷P3399 丝绸之路
传送门
是个比较水的题
整理下题意:
有n+1个城市从0到n排成一条链,初始时位于第0座城市,要前往第n座城市
每天可以选择走或者不走,每天最多走一座城市
问到达第n座城市最少消耗多少疲惫值,消耗的疲惫值与城市和天数有关.
确定状态: 设 dp[i][j] 表示第j天到达第i座城市最少消耗多少疲惫值
状态转移:
当前状态可能从两种状态转移过来
1.昨天就在这座城市,昨天选择休息
2.昨天在上一座城市,昨天选择走
比较容易推出状态转移方程:
dp[i][j]=min(dp[i-1][j],dp[i-1][j-1]+d[i]c[i])
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,dp[1011][1021],d[1023131],c[10231313];
//dp数组表示第i座城市,在第j天到达,最少消耗的疲惫值
signed main(){
//输入:
cin>>n>>m;
for (int i=1;i<=n;++i) {
cin>>d[i];
}
for (int i=1;i<=m;++i) {
cin>>c[i];
}
//初始化:
memset(dp,0x3f,sizeof(dp)); //因为要取min,所以先赋成极大值
for (int i=0;i<=m;++i) {
dp[0][i]=0;
//第0座城市不需要耗费体力,因此不论第几天都是0
}
//状态转移:
for (int i=1;i<=n;++i) {
for (int j=i;j<=m;++j) {
//因为一天最多走一个城市,所以最早也要第i天才能到第i座城市,所以循环从j=i开始
dp[i][j]=min(dp[i][j-1],dp[i-1][j-1]+d[i]*c[j]);
}
}
cout<<dp[n][m];
return 0;
}
洛谷P1564 膜拜
传送门
做这个题的时候看到题解提到前缀和就感觉不妙,想起了NOIP2022第一题的痛苦经历 实际做的时候也确实调了好久,看来前缀和掌握的还是有些生疏了
简要概括一下这个题:
给定一个长度为n,由1和2组成的数列。要求对这个数列划分区间。
每个区间只需要满足下列条件中的一个即可;
1.区间里全为1
2.区间里全为2
3.区间里1和2的个数之差不能超过m
求最少需要划分成几个区间才能使所有区间都满足上述条件
确立状态:
设dp[i]表示前i个人最少需要几个机房(前i个数最少划成几个区间)
状态转移:
用两个for循环,i从1循环到n,j从1循环到i
表示第i到j个人放到同一个机房里(i,j分别为区间的左右端点。)
判断是否合法后进行状态转移。
转移方程非常好求 dp[i]=min(dp[i],dp[j-1]+1);
但限制条件需要推一下(调了好久)
用前缀和sum数组表示前i个人里喜欢1的和喜欢2的人数之差,当sum[i]绝对值小于m时,或者sum[i]的绝对值等于i时,说明从1到i个人可以放到一个机房。再利用一下前缀和的性质,推出限制条件为:
if (abs(sum[i]-sum[j-1])<=m||abs(sum[i]-sum[j-1])==i-j+1) dp[i]=min(dp[i],dp[j-1]+1)
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,dp[1023122],a[102123],sum[1023311];
signed main(){
cin>>n>>m;
for (int i=1;i<=n;++i) {
cin>>a[i];
sum[i]=sum[i-1];
if (a[i]==1) sum[i]+=1;
else sum[i]+=-1;
}
memset(dp,0x3f,sizeof(dp));
dp[0]=0;dp[1]=1;
for (int i=1;i<=n;++i) {
for (int j=1;j<=i;++j) {
if (abs(sum[i]-sum[j-1])<=m||abs(sum[i]-sum[j-1])==i-j+1) {
dp[i]=min(dp[i],dp[j-1]+1);
}
}
}
cout<<dp[n];
return 0;
}