华为oj 合唱队

来源:互联网 发布:淘宝与京东 编辑:程序博客网 时间:2024/06/04 18:48

上一篇文章 说华为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 100int 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
原创粉丝点击