传送门:第45届ICPC沈阳站F题
标签:动态规划
题目大意
给定一个长度为n的序列(1<=n<=1e6,1<=ai<=1e9),你可以对其进行多次以下操作:将该序列中任意连续的一段构成一个组。要求最终原序列每个数都在组内,且在对各个组内的元素进行升序排列后,整个序列都呈升序排列。求:在满足条件的情况下最多能构成多少组?
输入:n,ai(i=1,2,3···n)
输出:组数的最大值
算法分析
- 乍看一下似乎是贪心,但还是要先模拟才能得到结论。首先可以确定一点:将原序列升序排列得到的结果是唯一的,也就是说不管怎么分组,原序列第k大的数最终一定会在下标为k的位置。那么我们可能会想到先把复制一个原序列,sort后得到升序序列,再找每个数在原序列里的位置,最后贪心得到答案。很显然这样做是错的,一是因为时间复杂度太大,二是因为序列中同一个数可以出现多次。
- 我们不妨从组与组之间的关系入手。手捏几个数据观察后不难发现,后一个组的最小值一定大于或等于前一个组的最大值。再分析一下题意:既然只能对在同一组内的元素进行排序,还要使排序后整体递增,那么显然各组之间是整体递增的。比如样例的数据:1 3 2 7 4,分组后变成了[1] [3 2] [7 4],3是序列中第3大的数,整体排序后就在第3位,依然在原来的组内。既然各个组是相互独立的,那么排在后面的组的每一个元素都要大于前面组的元素。
- 有了结论,我们就要考虑如何实现了。如果学过动态规划就会很快发现:这是最长上升子序列的变式!建立每一个组时,如果下一个数比上一个组的最大值大且比当前组的最大值大,就开一个新的组;如果比上一个组的最大值大但比当前组的最大值小,就留在这个组;而如果比上一个组的最大值小,就二分找前面最大值比当前数大的第一个组并替换为当前组最大值,这样就能以最大nlogn的时间复杂度解决这题。
代码实现
#include <iostream>
using namespace std;
#include <cstring>
int maxs[1000001]={0};
int main(){
int i,x,l,r,mid,n,top=1;
std::ios::sync_with_stdio(false);
std::cin.tie(0);std::cout.tie(0);
cin>>n; //序列元素数
for(i=0;i<n;i++){
cin>>x;
if(maxs[top-1]<=x) //比上一个组最大值大
if(maxs[top]<=x)maxs[++top]=x; //比当前组最大值大
else ; //防止if和else匹配错误,所以加了个else
else{
l=2;r=top-1; //二分找比当前数大的第一个数
while(l<r){
mid=(l+r)/2;
if(maxs[mid]<=x)l=mid+1;
else r=mid;
}
maxs[l]=maxs[top]; //替换最大值
top=l; //更新组数
}
}
cout<<top-1; //输出答案
return 0;
}