以洛谷P1091 合唱队形初探动态规划DP

2 篇文章 0 订阅

目录

一 题目描述 

二 题目拆解

三 子问题求解 

Q1:如何求dpup[i]——删除元素数最小的递增子序列

Q2:如何求dpdown[i]——删除元素数最小的递减子序列

四 AC代码


一 题目描述 

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值