【暴力Treap 或 离线归并】子串计数(genies)

子串计数(genies)

Description

给出一段含有n个元素的序列a,要求求出子串和小于等于t的子串个数

Input Data

输入共两行
第一行包含两个整数,n,t分别表示序列a元素的个数和限制t
第二行包含n个数表示元素a_i

Output Data

共一行,含一个数
表示子串和小于等于t的子串个数。

 Input / Output Sample

input #1:

5 4 5 -1 3 4 -1

output #1:
7

 

Solution:

一道妙题,首先分析性质。如果从L+1到R可以构成一个合法的区级显然s[R]-S[L]<=t,且L<=R;

前缀和的想法是显然的,但是还是突破不了枚举子串的瓶颈。

假设当前枚举到第i个元素那么我要从s[0]-s[i-1]找到尽可能多的s[j] (j∈[0,i-1])使s[i]-s[j]<=t,不妨考虑移项

s[j]>=s[i]-t,而此时s[i]-t是定值!!!我们只需要统计前面的s[j]有多少个s[j]>=s[i]-t就行了!即求s[i]-t的排行rank,

n-rank就是答案!

然而这种方法不是很妙,更妙的方法是这样的:

首先我们知道归并排序只会把他分成越分越小,而不会改变他原序列的前后顺序。

对于每一次归并的[L,T]和[T+1,R],在每一段都是升序排序的,我们弄一个指针pt1指在[L,T]弄另外的指针pt2指在[T+1,R]

对于s[pt1]不断的把pt2往右移动,找到第一个不能满足s[pt2]-s[pt1]<=t的点(前面可以满足的记录)。这样可以保证处理2段区间的复杂度是O(n)的

对于全部的数据复杂度显然是O(n log n)的。

Code:

 

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N=200020;
int n;
int t,ans=0; 
int s[N];
void solve(int l,int r)
{
    if (l>=r) return;
    int mid=(l+r)>>1;
    solve(l,mid); solve(mid+1,r);
    int ret=0;
    for (int i=l,j=mid;i<=mid;i++) {
        while (j<r&&s[j+1]-s[i]<=t) j++;
        ret+=j-mid;
    }
    ans+=ret;
    inplace_merge(s+l,s+mid+1,s+r+1);
}
signed main()
{
    scanf("%lld%lld",&n,&t);
    for (int i=1;i<=n;i++) {
        int x; scanf("%lld",&x);
        s[i]=s[i-1]+x;
    }
    solve(0,n);
    printf("%lld\n",ans);
    return 0;
}

 

posted @ 2019-01-29 21:04  ljc20020730  阅读(136)  评论(0编辑  收藏  举报