Scx117
只一眼,便辽阔了时间。

题意:定义一个串是k-happy的:对于所有的Ai,都有Aj(j!=i),使得|Ai-Aj|<=k。

问使得原串至少存在一个长度>=m的连续子串是k-happy的最小的k?

 

标程:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int Max=1e7;
 4 const int N=200005;
 5 int rg[N],lf[N],a[N],n,m,l,r,ans,res,bit[N],L,R,b[N];
 6 int lowbit(int x){return x&(-x);}
 7 void add(int x,int y){while (x<=n) bit[x]+=y,x+=lowbit(x);}
 8 int qry(int x){int res=0;while (x) res+=bit[x],x-=lowbit(x);return res;}
 9 int find(int x)//bit二分找rank(x) 
10 {
11     int l=0,r;
12     for (int i=20;i>=0&&x;i--)
13       if ((r=l+(1<<i))<=n&&bit[r]<x) x-=bit[l=r];
14     return l+1;
15 }
16 bool cmp(int x,int y){return a[x]<a[y];}
17 void init(int k)
18 {
19   L=1;R=1;
20   for (int i=1;i<=n;i++)
21   {
22          while (R<=n&&a[b[R]]<=a[b[i]]+k) add(b[R++],1);
23          while (L<=n&&a[b[L]]<a[b[i]]-k) add(b[L++],-1);
24          int t=qry(b[i]),sum=qry(n);
25         lf[b[i]]=t==1?0:find(t-1);rg[b[i]]=t==sum?n+1:find(t+1); 
26   }
27   while (L<=n) add(b[L++],-1);
28 }
29 void solve(int l,int r)
30 {
31   if (r-l+1<m||res>=m) return;
32   int L=l,R=r,pos=0;
33   while (L<=R)
34   {
35     if (lf[L]<l&&rg[L]>r) {pos=L;break;}
36     if (lf[R]<l&&rg[R]>r) {pos=R;break;}
37     L++;R--;
38   }
39   if (!pos) res=max(res,r-l+1);
40   solve(l,pos-1);solve(pos+1,r);
41 }
42 int main()
43 {
44   scanf("%d%d",&n,&m);
45   for (int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=i;
46   sort(b+1,b+n+1,cmp);
47   l=0;r=1e7;
48   while (l<=r)
49   {
50     int mid=(l+r)>>1;res=0;
51     if (init(mid),solve(1,n),res>=m) ans=mid,r=mid-1;else l=mid+1;
52   }
53   printf("%d\n",ans);
54   return 0;
55 }

 

题解:二分答案+启发式分治+树状数组二分

一开始读错题,以为是排序后两边的差都要满足,我写set+二分答案然后发现是假算法。。。

没想到可以把不合法的点拎出来分割开序列。

首先二分一个答案k,然后用权值线段树(但是常数较大)/树状数组可以预处理出[a[i]-k,a[i]+k]范围内离i最近(在左的和在右的)的点,连边。权值线段树是按照位置顺次一个一个加入,求权值范围内的区间最小值/最大值,正反都做一遍。树状数组是按照权值排序后双指针在树状数组上按位置维护权值范围内所有点,然后(类似普通平衡树)求前驱后继。

然后每次在序列中找一个左右连边点都在区间外的点,这个点一个不能选,由此删去这个点,把序列分成两部分。

如果顺次找,每次都找到的最后一个时间复杂度为O(n^2logn)。而每次从两边找,T(n)=T(a)+T(n-a)+min(a,n-a),这样就是启发式合并的复杂度,O(nlog^2n)。

还有一种做法,从后往前枚举左端点,运用字符串的思想统计前缀的答案。用线段树维护后驱覆盖(右开区间),如果新加入的一个点和后面的比它大的点x、比它小的点y匹配上(定义为上述的可以连边),如果这两个点没有前驱匹配,那么需要删除该点的后驱覆盖,和新加入点连边。若m个以后的位置还存在一个点没有被覆盖(值为0),那么取这个点为右端点则符合要求。(区间最小值)。常数较大。

 

posted on 2018-05-21 13:50  Scx117  阅读(161)  评论(0编辑  收藏  举报