【HDU5532 2015长春赛区F】【LIS+剪枝】Almost Sorted Array 最多移除一元素后单调 O(n)
2015-11-03 13:57
453 查看
#include<stdio.h> #include<string.h> #include<ctype.h> #include<math.h> #include<iostream> #include<string> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);} #define MS(x,y) memset(x,y,sizeof(x)) #define MC(x,y) memcpy(x,y,sizeof(x)) #define MP(x,y) make_pair(x,y) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1,class T2>inline void gmax(T1 &a,T2 b){if(b>a)a=b;} template <class T1,class T2>inline void gmin(T1 &a,T2 b){if(b<a)a=b;} const int N=1e5+10,M=0,Z=1e9+7,ms63=1061109567; int casenum,casei; int a ,d ; int n; bool goup() { int p=0,tmp=0;a[0]=-1e9; for(int i=2;i<=n;i++) { if(a[i]<a[i-1]) { if(p) { a[p]=tmp; return 0; } p=i; tmp=a[i]; if(a[i]>=a[i-1]||a[i]<a[i-2])a[i]=a[i-1]; } } a[p]=tmp; return 1; } bool godown() { int p=0,tmp=0;a[0]=1e9; for(int i=2;i<=n;i++) { if(a[i]>a[i-1]) { if(p) { a[p]=tmp; return 0; } p=i; tmp=a[i]; if(a[i]<=a[i-1]||a[i]>a[i-2])a[i]=a[i-1]; } } a[p]=tmp; return 1; } /* LIS(LDS)的O(nlogn)做法的思路是这样: 以最长不下降子序列为例,我们枚举所有的a[]尝试插入。 然后用d[]维护一个单调不下降的队列,作为辅助数组。 如果a[i]>=d[len],那么可以直接插入,d[++len]=a[i]; 否则我们不可以直接插入在最后,因为单调队列中存在数比当前的数大。 但是这个数还可能依然有意义,它可以把某个位置的数更新得更小,从而利用LIS的拓展。 于是,我们便可以二分一个位置l,使得这个位置尽可能靠前,且这个位置的数严格比当前的数要大。 然后我们就可以使得d[l]=a[i],这样我们向更小的趋势更新了LIS,使得它最后的len就是其长度。 */ int LIS() { bool flag=0; int len=0;d[0]=-1e9; for(int i=1;i<=n;i++) { if(a[i]>=d[len])d[++len]=a[i]; else { if(flag)return 0; flag=1; int l=1;int r=len; while(l<r) { int m=(l+r)>>1; if(a[i]<d[m])r=m; else l=m+1; } d[l]=a[i]; } } return len; } int LDS() { bool flag=0; int len=0;d[0]=1e9; for(int i=1;i<=n;i++) { if(a[i]<=d[len])d[++len]=a[i]; else { if(flag)return 0; flag=1; int l=1;int r=len; while(l<r) { int m=(l+r)>>1; if(a[i]>d[m])r=m; else l=m+1; } d[l]=a[i]; } } return len; } int main() { scanf("%d",&casenum); for(casei=1;casei<=casenum;casei++) { scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&a[i]); //goup()||godown()?puts("YES"):puts("NO"); LIS()>=n-1||LDS()>=n-1?puts("YES"):puts("NO"); } return 0; } /* 【trick&&吐槽】 1,变量注意别写错,样例太弱 input 4 4 3 6 5 output NO 2,nlog(n)写法可以加了剪枝。 加了剪枝后,复杂度后其实也近乎于O(n) 【题意】 给你一个长度为n(1e5)的数列a[](1<=a[]<=1e5)。 如果这个数列保持单调不上升或单调不下降,那么我们就说这个数列是有序的。 如果这个数列最多移除一个数后,剩下的数保持单调不上升或单调不下降,那么我们就说这个数列是近乎有序的。 现在问你给定的数列是否是近乎有序的。 【类型】 LIS最长上升子序列 【分析】 这道题的第一种做法是用LIS做,球场上升子序列l是否>=n-1,时间复杂度为O(nlogn) 第二种做法是直接暴力扫描。检测该数列是否单调不上升或单调不下降。 然而我们允许筛掉一个数。 所以以判定单调不下降为例,如果现在还有删数的能力。 而此时一旦出现了a[i]<a[i-1],那么这2个数中就必然要移除一个。 现在的问题是,移除谁呢? 我们为了保持最大单调不下降子序列,就后效性而言,我们希望保留两者较小的一个。 然而还要考虑前导性质,即a[i-1]>=a[i-2]这个肯定保证了的,所以我们如果保留a[i],必要前提是a[i]>=a[i-2]。 即if(a[i]>=a[i-1]||a[i]<a[i-2])a[i]=a[i-1]; 如果现在没有删数的能力,又出现了逆序对,那就GG 当然,因为既要扫描上升,又要扫描下降,所以我们可以记录一下位置p和初始的a[i]数值。用于还原数列。 【时间复杂度&&优化】 O(n) 线性扫描法 O(nlogn) LIS法 -> flag剪枝后变成 O(n) */
相关文章推荐
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法
- Ruby实现的合并排序算法
- C#折半插入排序算法实现方法
- 基于C++实现的各种内部排序算法汇总
- C++线性时间的排序算法分析
- C++实现汉诺塔算法经典实例
- PHP实现克鲁斯卡尔算法实例解析
- C#获取关键字附近文字算法实例