目录
Q2:如何求dpdown[i]——删除元素数最小的递减子序列
一 题目描述
P1091 [NOIP2004 提高组] 合唱队形 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
入门果然耗时间,一道简单的DP却花了我两三个小时
二 题目拆解
首先,将该题转化为数学题:给定一个数列,可以删除任意个元素,使之成为先严格递增后严格递减的数列,求删除元素数的最小值
我们知道,动态规划三步走:确定dp数组含义、找出dp数组递推式、定初值
这道题的入题核心是dp[i]代表求得以i为转折点,左边是以i为终点的递增序列,右边是以i为起点的递减序列所需删除元素个数的最小值
这几个定语,特别是以i为起点和终点,绝对不可忽略
本篇数组取值范围为1~N
dpup[i]为形成以i为终点的递增子序列所需出队的人数
dpdown[i]为形成以i为起点的递减子序列所需出队的人数
则dp[i]=dpup[i]+dpdown[i]
最后求min(dp)即可
相当于这题考察了两个非传统DP问题
三 子问题求解
Q1:如何求dpup[i]——删除元素数最小的递增子序列
也就是求以i为结尾的最长递增子序列的元素数k,i-k即为所求
易错点是忽略以i为结尾!
如果不要求以i为结尾,那么
下面以dpk存放k
接下来是确定扫描方向,从左向右扫描还是从右向左扫描,这里我们选择从1到i,也就是从左向右
4 3 1 2 5
dpk[i] 1 1 1 2 3
递推公式怎么求?假设我们要求dpk[i],由于我们从左往右求,那么dpk[i-1]已知,当array[i]>array[i-1]时,我们将array[i]加入子序列即可,显然有dpk[i]=dpk[i-1]+1
如果不满足该条件,则dpk[i]=1
即dpk[i]= {1,array[i]<=array[i-1]
{dpk[i-1]+1,array[i]>array[i-1]
dpup[i]=i-dpk[i], dpk[i]=i-dpup[i] -dpk[i-1]=+dpup[i-1]-i+1
下面考虑初始值,dpup[1]=0
即当i>=2时,dpup[i]= {i-1,array[i]<=array[i-1]
{dpup[i-1],array[i]>array[i-1]
但是现在要求以i为结尾,我们需要从1到i-1逐个比较得出临时值,再求出临时值的最小值
记临时值数组为dpt,有dpt[i]=i-dpk[i], dpk[i]=i-dpt[i](×)
dpk[i]=i-dpt[i]这个公式对么?不对,为什么?dpt是个临时数组,值是不断变动的,我们的本意是dpk[k]是以索引k所在元素为结尾的最长递增子序列的元素个数,这个值是个定值,具有无后效性,dpup具有无后效性,所以
dpk[i]=i-dpup[i]
比如要求dpup[5],那么dpup[1]~dpup[4]已知,我们要一一比较1~4,记当前被比较的索引为k,那么
如果array[i]>array[k],那么相当于array[i]加入以k结尾的子序列,即dpk[i]=dpk[k]+1
dpt[i] =i-dpk[i]
=i-dpk[k]-1
=dpup[k]+i-k-1
如果array[i]<=array[k],那么dpk[i]=1,dpt[i]=i-1
所以,当i=1时,dpk[1]=1,
当i>=2,k取1~i-1时,
dpt[i]= {i-1,array[i]<=array[k]
{dpup[k]+i-k-1,array[i]>array[k]
我们的目的是为了获取最小值,所以dpup[i]作为最小值记录器,记录遍历过程中的最小值即可
即dpup[1]=0,i>=2时
dpup[i]=min(dpt[i]遍历k从1~i-1)
———————————————————————————————————————————
Q2:如何求dpdown[i]——删除元素数最小的递减子序列
我们换个思维,从右向左扫描,那不就是求以i结尾的递增了么,同Q1,不过遍历方向需要改一下
现要求i为起点,需要从i+1~N逐个比较得出临时值,再求出临时值的最小值,记临时值数组为dpt,dpk[i]为以i为起点的递减子序列,也是倒序遍历以i为终点的递增子序列,有
dpt[i]=N-i+1-dpk[i], dpk[i]=N-i+1-dpdown[i]
比如要求dpup[5],N=10,那么dpup[6]~dpup[10]已知,我们要一一比较6~10,记当前被比较的索引为k(k从i+1~N),那么
如果array[i]>array[k],那么相当于array[i]加入以k起点的子序列,即dpk[i]=dpk[k]+1
dpt[i] =N-i+1-dpk[i]
=N-i+1-(dpk[k]+1)
=N-i-dpk[k]
=N-i-(N-k+1-dpdown[k])
=dpdown[k]+k-i-1
(很神奇,相比于Q1,i和k仅仅取了个反,不过k的遍历范围也变为了i+1~N)
如果array[i]<=array[k],那么dpk[i]=1,dpt[i]=N-i
所以,当i=N时,dpk[N]=1,
当0<i<N,k取i+1~N时,
dpt[i]= {N-i,array[i]<=array[k]
{dpdown[k]+k-i-1,array[i]>array[k]
我们的目的是为了获取最小值,所以dpdown[i]作为最小值记录器,记录遍历过程中的最小值即可
即dpdown[N]=0,0<i<N时
dpdown[i]=min(dpt[i]遍历k从1~i-1)
由公式dp[i]=dpup[i]+dpdown[i],得dp[i],求min(dp)即可
四 AC代码
#include<cstdio>
#include<iostream>
using namespace std;
int N;
int array[101];
int dpup[101];
int dptup[101];
int dpdown[101];
int dptdown[101];
int dp[101];
int main(){
scanf("%d",&N);
for(int i=1;i<=N;i++) scanf("%d",array+i);
dpup[1]=0;
dpdown[N]=0;
for(int i=2;i<=N;i++){
dpup[i]=0x7f7f7f7f;
for(int k=1;k<i;k++){
if(array[i]<=array[k]) dptup[i]=i-1;
else dptup[i]=dpup[k]+i-k-1;
if(dptup[i]<dpup[i]) dpup[i]=dptup[i];
}
}
for(int i=N-1;i>=1;i--){
dpdown[i]=0x7f7f7f7f;
for(int k=N;k>i;k--){
if(array[i]<=array[k]) dptdown[i]=N-i;
else dptdown[i]=dpdown[k]+k-i-1;
if(dptdown[i]<dpdown[i]) dpdown[i]=dptdown[i];
}
}
int ans=0x7f7f7f7f;
for(int i=1;i<=N;i++){
dp[i]=dpup[i]+dpdown[i];
if(dp[i]<ans) ans=dp[i];
}
printf("%d",ans);
return 0;
}