【线型DP】CF1012C Hills 小山坡

来了来了。


题目:

给你n个数,你一次操作可以把某一个数-1(可以减为负数),你的目标是使任意的k个数严格小于它旁边的两个数(第一个数只用严格小于第二个数,第n个数只用严格小于第n-1个数),问最少需要几次操作。k是不确定的,请输出1<=k<=n/2(向上取整)时的答案。

输入格式:

第一行一个正整数n

第二行n个正整数ai

输出格式:

一行 1<=k<=n/2 个数,第i个数代表k=i时的答案

数据范围:

1 ≤ n ≤ 5000

1 ≤ ai ≤ 100000

输入 #1
5
1 1 1 1 1
输出 #1
1 2 2 
输入 #2
3
1 2 3
输出 #2
0 2 

思路:

看到这道题的第一眼,我想到了一道题,2018noipD1T1铺设道路(链接:https://www.luogu.com.cn/problem/P5019),看起来十分相似,于是这道题想用朴素的贪心写,结果果然不对,两道题的区别还是很大,贪心的正确性无法保证,所以还是回来老老实实的写动归。

我们设定一个三维数组f,f[i][j][0]表示前i座山,修建j座房子,且第i座山没有建房子的代价,f[i][j][1]表示前i座山,修建j座房子,且第i座山建有房子的代价。

通过简单的模拟我们可以得到,(1)f[i][j][0]会从f[i-1][j][0](直接从前一个状态搬过来,前一座山和这座山都不建房子,抹油代价)和f[i-1][j][1](前一座山已经建了房子,那么就要使这座山的高度低于前一座山的高度,代价为max(0,a[i]-a[i-1]+1),有0是因为可能这座山的高度原来就低于前一座山的高度)转移过来,转移方程为:f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]+max(0,a[i]-a[i-1]+1))   (2)f[i][j][1]的处理要比f[i][j][0]的处理相对复杂,在此我们明确一个观点,相邻的两座山不可能都建房子,并且在考虑这座山是否建房子的时候,可以只考虑这座山之前的状态,而不去考虑这座山之后的状态,这座山之后的状态我们可以在之后再处理,因此,f[i][j][1]可以从f[i-2][j-1][0]转移过来,因为第i-2座山没有建房子,所以第i-1座山的高度没有变化,代价为max(0,a[i]-a[i-1]+1)。f[i][j][1]同样可以从f[i-2][j-1][1]转移过来,因为第i-2座山已经建有房子,所以第i-1座山的高度可能会有变化,因此,总代价为:max(0,a[i-1]-(a[i-2],a[i])+1)(大家思考一下,为什么这里总代价不是:max(0,a[i-1]-min(0,a[i-1]-a[i-2]+1)-a[i]+1)) 答案(先思考再看答案):因为第i-1座山的高度一定比第i-2和i座山低,而这样做只能保证第i-1座山的高度低于第i-2座山的高度,无法保证第i-1座山的高度低于第i座山的高度,因此,我们也可以改成:max(max(0,a[i-1]-a[i]+1),max(0,a[i-1]-a[i-2]+1)) 贼长

代码:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<cmath>
 5 #include<algorithm>
 6 using namespace std;
 7 const int maxn=5e3+5,maxe=5e3+5,INF=0x3f3f3f3f;
 8 int n,m,a[maxn],f[maxn][maxn][3],ans;
 9 inline int read(){
10     int s=0,t=1;
11     char ch=getchar();
12     while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
13     while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
14     return s*t;
15 }//朴素快读 
16 int main(){
17     freopen("a.in","r",stdin);
18     n=read();
19     for(int i=1;i<=n;i++)a[i]=read();
20     a[0]=INF;
21     memset(f,0x3f,sizeof(f));
22     f[0][0][0]=f[1][0][0]=f[1][1][1]=0;//初始化 
23     for(int i=2;i<=n;i++){
24         f[i][0][0]=f[i-1][0][0];
25         for(int j=1;j<=(i+1)/2;j++){//这里我测了一下,不管是用floor+1还是用ceil都不行,神奇 
26             f[i][j][1]=min(f[i-2][j-1][0]+max(0,a[i-1]-a[i]+1),f[i-2][j-1][1]+max(max(0,a[i-1]-a[i]+1),max(0,a[i-1]-a[i-2]+1)));
27             f[i][j][0]=min(f[i-1][j][0],f[i-1][j][1]+max(0,a[i]-a[i-1]+1));
28         }
29         //cout<<floor(i/2)+1<<" "<<(i+1)/2<<endl;
30     }
31     for(int i=1;i<=(n+1)/2;i++)cout<<min(f[n][i][0],f[n][i][1])<<" ";
32     return 0;
33 }

嘤嘤嘤,溜了

猜你喜欢

转载自www.cnblogs.com/614685877--aakennes/p/12694549.html