华为oj 合唱队

上一篇文章 说华为oj 烂,还有一部分原因就是,难度分配不合理,这道题虽然不是很难,但也绝不应该放在初级的题目中。

题目如下


解析:如果没做过或者刚接触动态规划,这道题还是比较有难度的,对于题目刚开始比较费解,这道题就是找一个数列,先从小到大,再从大到小,要计算最少需要几位同学出列,就是找到所形成从小到大,再从大到小得数列的个数最多。那么我们可以联想到,动态规划中的经典问题“最长增长子序列”,当然"最长下降子序列"也是同理。这道题,如果我们循环整个数列,设 i ∈[1,k],那么在第i个同学Ti的位置上我们计算[T1,Ti]的“最长增长子序列”,设共有s个元素,同时计算[Ti,Tk]的"最长下降子序列",设共有t个元素,设x=s+t-1,减一的原因是s,t共同占有Ti,这样就计算了两遍。那么x就是当前位置所能形成的最长数列的元素个数,我们只要循环1-k,找到所有x中最大的,也就是需要出列同学个数最少的喽!形成的最后公式应该是这样:出列同学数=同学总数-max{x|i ∈[1,k]}.

那么说了这么一大堆核心就是怎么求最长增长子序列喽!

第一种方法时间复杂度o(n^2)

设a[1]至a[n]存储n个输入的数字,b[1]至b[n]存储,从开始到某位置上,以此位置的元素为结尾的最长上升子序列的长度,举个例子a[1]=1,a[2]=2,a[3]=5,a[4]=6,a[5]=3,可知b[1]=1,b[2]=2,b[3]=3,b[4]=4,b[5]=3(b[5]是等于3的哦)。

(1)显然,b[1]=1,只有一个数字,自己组成上升子序列。

(2)对于i∈[1,n],若a[i+1]>=a[i],那么b[i+1]=b[i]+1,反之,若a[i+1]<a[i],那么 需要找到一个位置k∈[1,i-1],满足a[k]<a[i+1]的所有位置中a[k] 为最大的值,即所有满足条件的位置中增长子序列长度最长的那个,那么,b[i+1]=b[k]+1。

根据上面列出,可以写出状态方程:b[i]=max{ b[j] | 1<=j<=i&&a[j]>=a[i] }+1。

根据此状态方程,程序应该比较好写,如下:

#include<iostream>

using namespace std;

int main()
{
	int a[500], b[500], n, max ;

	while (cin >> n)
	{
		max = 0;
		//获取输入
		for (int i = 0; i < n; i++)
		{
			cin >> a[i];
		}

		b[0] = 1;
		for (int i = 1; i < n; i++)
		{
			b[i] = 1;
			for (int j = 0; j < i; j++)
			{
				if (a[j] < a[i] && b[j] + 1>b[i])
				{
					b[i] = b[j] + 1;
				}
			}
		}

		for (int i = 0; i < n; i++)
		{
			if (b[i]>max)
			{
				max = b[i];
			}
		}
		cout << max << endl;
	}

	return 0;
}

第二种方法,时间复杂度O(n*logn)

同第一种方法,设a[1]至a[n]存储n个输入的数字,b[1]至b[n]存储,从开始到某位置上,所能达到的(不是以此位置的元素为结尾哦!!!)最长上升子序列的长度,举个例子a[1]=1,a[2]=2,a[3]=5,a[4]=6,a[5]=3,可知b[1]=1,b[2]=2,b[3]=3,b[4]=4,b[5]=4(b[5]是等于4的哦)。

 那么怎么计算b数组的值呢? 我们需要引入一个额外的数组s,s[i]表示,在所有能达到长度为i的增长子序列中元素值最小的那个,举个例子s[3],假设子序列中有1,2,7 和1,2,4达到长度为3那么 s[3]=4,那为什么要找最小的这个那,  如果我们遇到的下一个元素是5, 对于1,2,7 和1,2,4子序列,前者的增长子序列的长度还是3,但是后者就达到了4。 数组s和数组b又有什么关系呢?设s数组中拥有的元素个数为len,那么b[i]=len。

所以整个运算如下,设len为当前最长子序列的长度,对于i∈[1,n],比较a[i]和s[len],若a[i]>s[len] ,  len++,   s[len]=a[i],   b[i]=len,若a[i]<=s[len],在s数组中查找, 找到位置k,满足s[k]<a[i]同时满足k为能够取得的最大值,这时我们一定能满足此公式s[k]<a[i]<s[k+1],此时需要更新s[k+1]=a[i],也就是原来的增长子序列在结尾加上a[i] 就能够达到长度为k+1。

我们可以看出s 数组为递增的数组,所以在a[i]<=s[len],我们可以同过二分法查找s数组,这样,这个查找过程的时间复杂度就变成了O(logn),整体的时间复杂度就变成了O(nlogn)。

程序实现如下

#include <iostream>
using namespace std;


int main()
{
	int a[500], b[500], n;
	int i, low, high, mid,ans;
	int sol[500];
	
		while (cin >> n)
		{
			ans = 0;
			//获取输入
			for (int i = 0; i < n; i++)
			{
				cin >> a[i];
			}
	
			for (i = 0; i < n; i++) {
				low = 1; high = ans;
				while (low <= high) {//二分法查找
					mid = (low + high) >> 1;
					if (sol[mid] < a[i]) low = mid + 1;
					else high = mid - 1;
				}
				if (low > ans) ans++;
				sol[low] = a[i];
				b[i] = ans;
			}
	
			cout << ans << endl;
		}
		return 0;
}

核心实现完毕,整体代码就是多加了一层循环而已,代码如下

#include <iostream>
using namespace std;

#define max 100

int incsq[max], decsq[max];

void LongInSeq(int len,int *a)
{
	int low , high ,mid,ans=0;
	int sol[max];

	for (int i = 0; i < len; i++)
	{
		low = 1;
		high = ans;
		while (low <= high)//二分法查找, 找到满足sol[k] < a[i]最大的k,更新k+1,low就是k+1的位置
		{
			mid = (low + high) >> 1;
			if (sol[mid] < *(a+i))
			{
				low = mid + 1;
			}
			else
			{
				high = mid - 1;
			}
		}
		if (low > ans)
		{
			ans++;
		}
		sol[low] = *(a + i);
		incsq[i] = ans;
	}
	
}
void LongDeSeq(int len, int *a)
{
	int low, high, mid, ans = 0;
	int sol[max];

	for (int i = 0; i < len; i++)
	{
		low = 1;
		high = ans;
		while (low <= high)//二分法查找, 找到满足sol[k] > a[i]最大的k,更新k+1,low就是k+1的位置
		{
			mid = (low + high) >> 1;
			if (sol[mid] > *(a + i))
			{
				low = mid + 1;
			}
			else
			{
				high = mid - 1;
			}
		}
		if (low > ans)
		{
			ans++;
		}
		sol[low] = *(a + i);
		decsq[i] = ans;
	}
}

int main()
{

	int maxn = 0;
	int len, a[max],temp;

	cin >> len;
	for (int i = 0; i < len; i++)
	{
		cin >> temp;
		a[i] = temp;
	}

	LongInSeq(len,a);
	LongDeSeq(len,a);

	for (int i = 0; i < len; i++)
	{
		if (incsq[i] + decsq[len-i]-1>maxn)
		{
			maxn = incsq[i] + decsq[len-i]-1;
		}
	}
	cout << len-maxn << endl;

	return 0;
}
结论:动态规划是这种编程题很容易考的题,只有熟练掌握经典动态规划的算法,才能对其更好地应用。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值