bzoj3100 排列
这个题有点恶心啊,总觉得以前做过,可就是想不出来。最后在发现是meng神出的一次模拟赛题= =当时我还P都不会呢...
这个题有两个关键点,一个是选取的子序列内不能有重复,二是选取的子序列要是一个排列(当然这包含了一)。首先如果确定了不会重复,那么只要子序列内的数的和等于(len+1)*len/2就行了,那么怎么确定不重复呢?
我们可以O(N)时间求出来每个数两边第一个和它相同的数的位置(L,R),这样我们可以不断地更新合法的序列的范围。
具体方案:我们先确定每一个1的位置(因为每个区间首先必须有1),按照这个来划分区间。我们先从左到右扫每个区间,相当于枚举了右端点k(我们要包含那个1),同时记录最大值mx,作为更新答案的标准(因为最大值同时是排列的长度)。然后我们只要保证左端点是合法的就行了。我们不断地用每个数的L来更新左边界(左端点的最小值),这样只要保证k-mx+1在大于左边界就行了,再用前缀和判定是不是排列就行了。
这个题被空间卡的好惨,贴一个没优化空间的代码吧。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<cmath> 6 #define maxn 1200000 7 using namespace std; 8 int l[maxn],r[maxn],s[maxn],a[maxn],last[maxn],st[maxn]; 9 int n,m; 10 11 int main() 12 { 13 //freopen("perm.in","r",stdin); 14 scanf("%d",&n); 15 for (int i=1;i<=n;i++) 16 { 17 scanf("%d",&a[i]); 18 s[i]=s[i-1]+a[i]; 19 if (a[i]==1) st[++m]=i; 20 } 21 st[0]=0,st[m+1]=n+1; 22 for (int i=1;i<=n;i++) 23 { 24 l[i]=last[a[i]]; 25 last[a[i]]=i; 26 } 27 for (int i=1;i<=n;i++) last[i]=n+1; 28 for (int i=n;i;i--) 29 { 30 r[i]=last[a[i]]; 31 last[a[i]]=i; 32 } 33 int ans=0; 34 for (int i=1;i<=m;i++) 35 { 36 int j=st[i];//枚举1 37 int mx=0,rr=n+1; 38 for (int k=j;k>=st[i-1]+1;k--)//向左扫,确定左端点 39 { 40 mx=max(mx,a[k]);//区间中最大值(同时也是答案长度) 41 rr=min(rr,r[k]-1);//区间右端点最大值 42 if (k+mx-1<=rr&&s[k+mx-1]-s[k-1]==(mx+1)*mx/2) ans=max(ans,mx); 43 } 44 } 45 for (int i=m;i;i--) 46 { 47 int j=st[i];//枚举1 48 int mx=0,ll=0; 49 for (int k=j;k<=st[i+1]-1;k++)//向右扫,确定右端点 50 { 51 mx=max(mx,a[k]);//区间中最大值(同时也是答案长度) 52 ll=max(ll,l[k]+1);//区间中左端点最大值 53 if (k-mx+1>=ll&&s[k]-s[k-mx]==(mx+1)*mx/2) ans=max(ans,mx); 54 } 55 } 56 printf("%d\n",ans); 57 return 0; 58 }
AC without art, no better than WA !