【51nod1472】取余最大值

Description

有一个长度为n的数组a,现在要找一个长度至少为2的子段,求出这一子段的和,然后减去最大值,然后对k取余结果为0。

问这样的子段有多少个。

样例解释:下标从1开始,对应的三个区间为[1:3],[1:4],[3:4]

Solution

其实就是求最大值与和同余的区间个数。
先找到每个最大值控制的区间,这个可以建出笛卡尔树然后遍历一遍求出。
考虑同余的条件,我们可以求一遍a的模k前缀和,那么对于区间\((l,r]\)且被当前最大值控制,那么有:\((s[j]-s[i])mod\ k=mx\),那么可以想到把关于\(i\)的移向一边,开个数据结构维护某个区间某个值出现了多少次。
还会有一个问题,跨越当前最大值的区间,左边和右边端点个数可能不平均,那么我们选择端点少的一边枚举,另一边直接用数据结构统计。复杂度最坏是两边平均,也就是\(O(nlog_2n)\),所以总复杂度是\(O(nlog_2^2n)\)的。

Code

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#define fo(i,j,k) for(int i=j;i<=k;++i)
#define fd(i,j,k) for(int i=j;i>=k;--i)
using namespace std;
typedef long long ll;
const int N=3e5+10;
int a[N],st[N],s[N],rs[N];
int le[N],re[N];
int bz[N<<2];
vector<int> b[N<<2];
void pre(int x){
	if(!x) return;
	pre(le[x]),le[x]=le[le[x]]?le[le[x]]:x;
	pre(re[x]),re[x]=re[re[x]]?re[re[x]]:x;
}
int find(int z,int l,int r){
	int l1=lower_bound(b[z].begin(),b[z].end(),l)-b[z].begin();
	int r1=upper_bound(b[z].begin(),b[z].end(),r)-b[z].begin()-1;
	return r1-l1+1;
}
int main()
{
	int n,k,top=0;
	scanf("%d %d",&n,&k);
	fo(i,1,n){
		scanf("%d",&a[i]),s[i]=(s[i-1]+a[i])%k;
		while(top && a[i]>a[st[top]]) top--;
		le[i]=re[st[top]],re[st[top]]=i;
		st[++top]=i;
	}
	re[0]=0;
	pre(st[1]);
	ll ans=0;
	fo(i,0,n) b[s[i]].push_back(i);
	fo(i,1,n)
	if(re[i]-i<i-le[i]){
		fo(j,i,re[i])
		ans+=find(((s[j]-a[i])%k+k)%k,le[i]-1,i-1);
	}
	else{
		fo(j,le[i],i)
		ans+=find((s[j-1]+a[i])%k,i,re[i]);
	}
	printf("%lld",ans-n);
}

posted @ 2018-06-21 16:25  sadstone  阅读(136)  评论(0编辑  收藏  举报