Description

有一个长度为n的数组a,现在要找一个长度至少为2的子段,求出这一子段的和,然后减去最大值,然后对k取余结果为0。
问这样的子段有多少个。
样例解释:下标从1开始,对应的三个区间为[1:3],[1:4],[3:4]

Solution

很有意思的一道题目。
首先,我们做最大值,很容易想到,找到所有的最大值,然后去扩展区间,可以用O(n)预处理。出l,r数组。
如果最大值为a,那么要判定的问题就是s[r]−s[l−1]−a≡0(modm),因为这段区间一定要包含a,那么可以直接枚举l~a左边后a右边到r,然后判断右边或左边又多少个数等于a[l-1]+a或a[r]-a。(如果左边比右边小就枚举左边,否则就枚举右边,复杂度n log n)
现在问题就转化成判断一个区间有多少个为p的数。
我想到了两种做法:

方案1:

因为要在l到r里面找有多少个为p的数,那么相当于个p开一个桶c[p][r]-c[p][l-1]就是答案,但是空间不够开这么大的桶,怎么办?
我们发现上面的东西可以拆成+c[p][r]和-c[p][l-1],那么我们可以在每个位置处理有哪些数的个数要被加或减,开一个vector来处理。
比如说上面再r位置要加一下p的个数,l-1的位置要减一下p的个数。
然后到最后在统计答案用cnt处理一下每个数出现的次数,然后就直接做就好了。

方案2

一开始就用vector处理一下值为p的所有位置。
那么要对l-1和r操作,那么二分一下就可以了。
c++当然用lower和upper就好了。
我打了方案2。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=600007;
int i,j,k,t,n,m,l[maxn],r[maxn],mo,a[maxn],s[maxn];
ll ans;
vector<int>d[1000007];
int ask(int x,int l,int r){
int yi=upper_bound(d[x].begin(),d[x].end(),r)-d[x].begin()-1,
er=lower_bound(d[x].begin(),d[x].end(),l)-d[x].begin()-1;
return yi-er;
}
int main(){
// freopen("fan.in","r",stdin);
scanf("%d%d",&n,&mo);
fo(i,1,n){
scanf("%d",&a[i]);
s[i]=(s[i-1]+a[i])%mo;
}
fo(i,0,n)d[s[i]].push_back(i);
fo(i,1,n){
l[i]=i-1;
while(l[i]>=1&&a[l[i]]<a[i])l[i]=l[l[i]];
}
fod(i,n,1){
r[i]=i+1;
while(r[i]<=n&&a[r[i]]<=a[i])r[i]=r[r[i]];
}
fo(i,1,n){
if(i-l[i]<r[i]-i){
fo(j,l[i]+1,i){
k=(s[j-1]+a[i])%mo;
ans+=ask(k,i,r[i]-1);
}
}
else{
fo(j,i,r[i]-1){
k=((s[j]-a[i])%mo+mo)%mo;
ans+=ask(k,l[i],i-1);
}
}
}
printf("%I64d\n",ans-n);
}