题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1153
题意:中文题诶~
思路:一个比较简单的方法是dfs隐式图搜索,不过用单调队列会快一点
1.先说一下隐式图搜索吧:
假设所有元素都满足条件的话,那么B数组元素就是给出数组排序后的下标啦,我们看一下样例。
9, 10, 2, -1, 3, -5, 0, -3, 1, 12, 5, 8, -2, 6, 4 排序后为:
12, 10, 9, 8, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -5 我们可以把12, 10, 9的下标加入B数组,不过8不行,因为8在12的右边,不满足8和9之间是所有元素都小于9这个条件。。。其实我们可以通过找区间最大值来解决这个问题,我们一开始将区间[l, r]定为全集,12为最大值,不难发现只要我们选取了区间[l, r]中的最大值的话,那么接下来选择区间[l, r]中的任意元素加入B数组都是可行的。12将这个区间分为了两部分,我们可以将剩下要加入B的元素从第一个或者第二部分集合里面选取。注意在一种B集合选取方案中我们不能两部分集合元素都选取,因为分别取自两部分集合的元素之间必定会有12这个最大元素啦。。。
我们看下当前状态,我们已经选取了12这个元素,剩下的元素可以全部从9...1或者5...4两个集合中选取,答案就是这两种情况中最大的啦。。。
我们很容易发现接着从集合9...1或者5...4中选取元素和我们一开始的情形是一样的。。。那么我们只要不停的选取当前区间的最大元素,再递归处理其两个子区间就ok啦。。。
代码:
1 #include <iostream>
2 #define MAXN 50010
3 using namespace std;
4
5 int a[MAXN];
6 const int inf=-1;
7
8 int dfs(int l, int r){
9 if(l==r){ //***注意边界情况有两种,可能我们上一层递归中的最大元素mid是l, 或者l后一个元素
10 return 1;
11 }else if(l>r){
12 return 0;
13 }
14 int gg=inf, mid=0;
15 for(int i=l; i<=r; i++){
16 if(a[i]>gg){
17 gg=a[i];
18 mid=i;
19 }
20 }
21 return max(dfs(l, mid-1), dfs(mid+1, r))+1; //***递归两个子区间,答案是较大的值
22 }
23
24 int main(void){
25 ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
26 int n;
27 cin >> n;
28 for(int i=0; i<n; i++){
29 cin >> a[i];
30 }
31 int ans=dfs(0, n-1);
32 cout << ans << endl;
33 return 0;
34 }
递归时最大元素是随机的,时间复杂度和快排差不多,O(nlogn)
2.单调队列
通过上面的描述我们最大每个最大值都会将一个区间分成两个子区间(可以为空),那么我们是不是可以通过顺序遍历每个区间最大元素来找到答案?
我们可以通过单调队列来处理,我们用结构体数组gg存储队列,gg[i].x, gg[i].num分别存储第i个元素的值和从上一个区间最大值元素开始到当前元素B可以的最大长度(为了得到区间最大值的左儿子区间元素个数)。
那么对于当前元素x有两种情况:
1. x大于当前单调队列队尾的元素,即其为上一个区间最大值到x这个区间的区间最大值,维护单调队列我们删除从队尾开始所有比x小的元素即删除x这个最大值的所有左儿子区间元素,并获取其左儿子区间的元素个数。当我们处理到全集最大元素时就能获得其向左拓展的情况下的最优解啦。。
2. 若x不大于当前队尾元素的话,即其为上一个区间最大值的右儿子元素,用右儿子数目加上当前队列里面的元素数目,处理到最后一个区间最大值时就能得到向右拓展的最优解啦。。
我们取两者中的较大值就是答案了。。
感觉这个算法其实就是对前面dfs的模拟的,虽然效率提高到了O(n),不过不是很好理解额。。。
代码:
1 #include <iostream>
2 #define MAXN 50010
3 using namespace std;
4
5 struct node{ //***gg[i]存储第单调队列第i个元素的值和从上一个区间最大值元素到第i个元素B的最大长度
6 int x, num;
7 }gg[MAXN];
8
9 int main(void){
10 ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
11 int n, x, index=0, ans=0;
12 cin >> n >> gg[0].x;
13 gg[0].num=1;
14 n--;
15 while(n--){
16 cin >> x;
17 int cc=0;
18 while(index>=0&&x>gg[index].x){ //***若出现比当前队尾大的数,即队列里上一个最大元素后面的元素都是当前最大元素的左儿子区间,
19 cc=max(cc, gg[index].num);//***那么我们令cc为为其左儿子区间里面最大可能多的B元素个数,其实就是x的左儿子数目啦
20 index--; //***删除比x小的元素
21 }
22 index++; //***将x加入单调队列,下标+1
23 cc++; //***x的右儿子区间为可加入B数组元素数目为cc,再加入x元素本身
24 cc=max(cc, index+1); //***index其实就是队里的元素个数加上队列里最后一个元素当前右儿子区间的数目
25 ans=max(ans, cc); //***更新ans
26 gg[index].x=x; //***更新单调队列元素
27 gg[index].num=cc;
28 }
29 cout << ans << endl;
30 return 0;
31 }