BSOJ 5100 简单的区间
题意:
看数据范围知复杂度
对于30%的数据
可以直接暴力n^2统计
对于另外的20%
并不知道怎么做,应该是给了一些基于随机的数据结构的分吧(例ODT,但不一定是这道题)
对于100%
考虑分治,我们可以把整个询问区间分成[1,mid]和[mid+1,n]
不断地分治下去就是[l,mid],[mid+1,r]
那么对于单点,容易发现f(i,i)不合法,直接返回,此时对答案的贡献为0
情况1:对于queryl和queryr都在[l,mid]中的区间,我们直接继续分治下去,queryl和queryr在[mid+1,r]当中同样如此(因为分治到最后总能变成情况2)
情况2:对于queryl属于[l,mid],queryr属于[mid+1,r]:
我们可以暴力统计:
1.记录l~mid的后缀和suf与后缀的最大值max1,以及mid+1~r的前缀和pre与前缀最大值max2
2.枚举i属于l~mid,此时用一个指针qr属于[mid+1,r]来求出:当前最小的可使得max{a[mid+1~qr]}>max{a[i,mid]}的位置,在循环枚举i的时候单调的往后移动就行了,在移动的时候,我们桶记录和为pre[ql]的数量即可
然后在枚举i的时候,对于每一个位置i,我们都在桶里面找到一个和suf[i]-max1[i]相加可以被k整除的pre[w](具体实现见代码),此时就让ans加上贡献,即这样的pre[w]的个数(在桶里O(1)询问即可)
3.枚举i属于mid+1~r,过程和上述方式一样
其实上面步骤2,3本质就是钦定了某个数作为了全局的max值(这个是动态的),而且二分的过程钦定了整个区间的sum,则我们可以做到统计这个sum
最后注意%k的处理即可
那么代码如下:
#include<bits/stdc++.h> using namespace std; template <typename T> inline void read(T &x){ x=0;char ch=getchar();bool f=false; while(!isdigit(ch)){if(ch=='-'){f=true;}ch=getchar();} while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x=f?-x:x; return; } template <typename T> inline void write(T x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10^48); } const int N=2000005; int n,k,a[N],pre[N],suf[N],t[N],max1[N],max2[N],ans; void solve(int l,int r){ if(l==r) return ; int mid=(l+r)>>1; pre[mid]=suf[mid+1]=max1[mid+1]=max2[mid]=0; for(int i=mid;i>=l;i--){ suf[i]=(suf[i+1]+a[i])%k; max1[i]=max(max1[i+1],a[i]); } for(int i=mid+1;i<=r;i++){ pre[i]=(pre[i-1]+a[i])%k; max2[i]=max(max2[i-1],a[i]); } int ql=mid,qr=mid+1; for(int i=mid;i>=l;i--){ while(qr<=r&&max2[qr]<=max1[i]) t[pre[qr]]++,qr++; ans+=t[(k-(suf[i]-max1[i]+k)%k)%k]; } for(int i=mid+1;i<qr;i++) t[pre[i]]--; for(int i=mid+1;i<=r;i++){ while(ql>=l&&max1[ql]<max2[i]) t[suf[ql]]++,ql--; ans+=t[(k-(pre[i]-max2[i]+k)%k)%k]; } for(int i=mid;i>ql;i--) t[suf[i]]--; solve(l,mid); solve(mid+1,r); return ; } int main(){ read(n),read(k); for(int i=1;i<=n;i++) read(a[i]); solve(1,n); write(ans); return 0; }