您的位置:首页 > 其它

【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)

*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ACM 算法 ICPC HDU 脑洞