合唱队形

合唱队形_dp

题目描述:

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,…,K1,2,…,K,他们的身高分别为T1,T2,…,TK ,则他们的身高满足T1<…Ti+1>…>TK(1<=i<=K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

注:这是洛谷P1091的题解,链接在下面: https://www.luogu.com.cn/problem/P1091
刚学到了单调队列和动态规划,就写了下面的题解. 自己是个算法小白,看到许多大佬已经发了很多这道题的题解,但是作为自己写的第一篇博客,还是想记录一下自己的想法。

大致思路:

这道题给的标签是dp和单调队列,不知道什么是单调队列的我赶紧去CSDN查了一下,刚开始还不知道为什么怎么用到单调队列,后来发现,不用单调队列,时间复杂度特别高(当然各位大佬可能有不用单调队列时间复杂度也很低的),不知道会不会TLE,不过对单调队列的认识还很浅,也不知道自己的解法是不是单调队列,还请各位大佬多指点。 下面开始讲解一下自己的思路。 这道题的切入点应该是站在中间的那个同学,依次计算出他左边和右边的最长递减序列,就可以计算出以他为中心的队伍长度。 根据动态规划的思想,我们这里设一个数组dp[100][2]。其中dp[i][0]表示以i号同学为中心,其左边的最长递减子序列的长度。dp[i][1]表示其右边的最长递减子序列的长度。(这里请牢记这个dp[][]数组的意义,下面多次会用到)

题解:

下面先讲解一下如何计算dp[i][0]:
转移方程为dp[i][0]=max(dp[i][0],dp[j][0]);(0<=j<i且h[i]>h[j])

假设总人数为n,每个人的身高依次存放在一个h[]数组中,我们用数组que[100]来模拟队列。que[i]存放左边递减序列长度同为i的最低的同学的身高。同时我们定一个一个变量len来记录队列中元素的个数。可能这里说的不是很清楚,下面用给的样例来演示一下。
n=8,八个人的身高依次为:


186186150200160130197220

i=0时,其对应的是第一个人,所以他左边递减序列为0,186直接入队列,如下:

0123
186

此时len=1;

之后从第一个元素开始遍历,开始查找在que[]中第一个比h[i]小的元素(注意不是小于等于)。如果查找的结果是没有比它小的,那么自然dp[i][0]=0;应该将que[0]更换为h[i]的值。

对于这里的解释,下面举一个例子,对于序列160 130 140,刚开始que[0]是等于160,当i等于1时,对应身高为130,比队列中的160要小,他的dp[i][0]也是0,那么既然160和130左边单调递减的最长子序列长度都是0,那么对于他们两个右边的元素而言,如果这个元素大于160那么它既可以把130加入自己的左队伍中,也可以选择130,但是自然是只能选择一个,但是如果这个元素大于130而小于160,那么他也是可以把130加入队伍中,所以这里把que[0]的值更新为130。

上面的que[]元素更新的问题已经讲过了,下面回到我们的样例,

i=1时:
由于没有比186小的元素,所以dp[1][0]=0,que[0]更新为186;
此时的队列:

0123
186

此时len=1;
i=2时:
对应的身高为150,同样没有比150小的元素,dp[2][0]=0;根据que[]数组的定义,他存放的是dp[][] 数值相同的元素中身高的最小值,150和186对应的dp[][0]都是0,而且150<186,所以此时que[0]更新为150:

0123
150

i=3时,对应身高为200,150比200要小,由于200是目前为止第一个dp[][0]=1的元素,所以直接把200追加在que[1]的位置,dp[3][0]=1,len=2;
此时的队列:

0123
150200

i=4时,对应身高为160,que[]中比他小的有一个元素150,所以dp[4][0]=1,与队列中que[1]的值进行比较,160<200,因此用160替换que[1]中的200,que[1]=160,len=2;

0123
150160

下面同理,最终的队列为:

0123
130197220

代码如下:

void dp0(){
	int i=0;
	dp[i][0]=0;
	que[0]=h[0];
	int len=1;
	//初始化
	for(i=1;i<n;i++){
		int t=len-1;//t用来记录第一个小于h[i]的元素的下标
		int key=h[i];
		while(t>=0&&que[t]>=key){
			t--;
		}
		que[t+1]=key;
		dp[i][0]=t+1; 
		if(t==len-1)
			len++;
	} 
}

对于dp[i][1]的计算,同dp[i][0],不过应该注意,dp[i][0]的计算是从左到右遍历,dp[i][1]是从右到左遍历,代码如下:

void dp1(){
  int i=n-1;
  dp[i][1]=0;
  que[0]=h[i];
  int len=1;
  for(i=n-2;i>=0;i--){
  	int t=len-1;
  	int key=h[i];
  	while(t>=0&&que[t]>=key){
  		t--;
  	}
  	que[t+1]=key;
  	dp[i][1]=t+1; 
  	if(t==len-1)
  		len++;
  }
}

当dp[][]计算完之后,就可以计算满足题干要求的最长序列了,ans=max(ans,dp[i][0]+dp[i][1]+1);注意加一,把自身也要计算进去。
完整的AC代码如下:

#include<bits/stdc++.h>
#define N 100
using namespace std;
int dp[100][2]={};	//dp[i][0]表示i号之前单调递减的最长序列的长度,dp[i][1]表示右端的 
int h[100]={};
int n;
int que[100]={};	//queue[i]=h[],i=dp[]; 
void dp0(){
	int i=0;
	dp[i][0]=0;
	que[0]=h[0];
	int len=1;
	//初始化
	for(i=1;i<n;i++){
		int t=len-1;
		int key=h[i];
		while(t>=0&&que[t]>=key){
			t--;
		}
		que[t+1]=key;
		dp[i][0]=t+1; 
		if(t==len-1)
			len++;
	} 
}
void dp1(){
	int i=n-1;
	dp[i][1]=0;
	que[0]=h[i];
	int len=1;
	for(i=n-2;i>=0;i--){
		int t=len-1;
		int key=h[i];
		while(t>=0&&que[t]>=key){
			t--;
		}
		que[t+1]=key;
		dp[i][1]=t+1; 
		if(t==len-1)
			len++;
	}
}
int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>h[i];
	}
	dp0();
	dp1();
	int ans=0;
	for(int i=0;i<n;i++){
		ans=max(ans,dp[i][0]+dp[i][1]+1);
	}
	cout<<n-ans<<endl;
	return 0;
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值