子串计数(genies)
Description
给出一段含有n个元素的序列a,要求求出子串和小于等于t的子串个数
Input Data
输入共两行
第一行包含两个整数,n,t分别表示序列a元素的个数和限制t
第二行包含n个数表示元素a_iOutput Data
共一行,含一个数
表示子串和小于等于t的子串个数。
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 ",ans); return 0; }