Codeforces 549F Yura and Developers

Description

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

Solution

考虑分治,普遍的做法就是用最大值分治:即找到最大值作为 \(mid\),然后 \(solve(l,mid),solve(mid+1,r)\)
但是这样做必须得保证每一次枚举 \(mid-l\)\(r-mid\) 中最小的一个才能保证复杂度,即启发式合并的复杂度 \(O(n*logn)\)

从上往下递归的话,每一次找最大值是一个复杂度瓶颈,考虑从下往上,每一次合并最大值即可

这个过程可以不用递归实现,我们按照从小到大的顺序枚举 \(a\) 中的每一个数,并且维护分治区间 \([L,R]\) 就可以达到这个效果
\([L,R]\) 的实际含义就是 \(mid\)\([L,R]\) 中的最大值

考虑 \(solve(l,r)\) 怎么统计答案,我们枚举 \(mid-l\)\(r-mid\) 中较小的一个 \(i\),那么 \(i\) 的贡献就是在 \([l,mid]\) 中与 \(sum[i]\) 相同的数的个数
\(sum\) 是模 \(k\) 意义下的前缀和,对于每一个 \(sum[i]\) 我们开一个数组(维护相同前缀和出现的不同位置),二分出 \([l,mid]\) 中出现的次数,就做完了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=300005;
vector<int>S[1000005];
int n,K,a[N],s[N],L[N],R[N],lis[N];ll ans=0;
inline bool comp(int i,int j){return a[i]<a[j];}
inline int query(int l,int r,int k){
	if(l>r)return 0;
	return upper_bound(S[k].begin(),S[k].end(),r)
		-upper_bound(S[k].begin(),S[k].end(),l-1);
}
inline void solve(int mid){
	int l=L[mid]+1,r=R[mid]-1;
	if(mid-l<r-mid){
		for(int i=l-1;i<mid;i++)
			ans+=query(max(mid,i+2),r,(s[i]+a[mid])%K);
	}
	else{
		for(int i=mid;i<=r;i++)
			ans+=query(l-1,min(mid-1,i-2),(s[i]-a[mid]+K)%K);
	}
	R[L[mid]]=R[mid];L[R[mid]]=L[mid];
}
int main(){
  freopen("pp.in","r",stdin);
  freopen("pp.out","w",stdout);
  scanf("%d%d",&n,&K);
  for(int i=1;i<=n;i++){
	  scanf("%d",&a[i]);
	  s[i]=(s[i-1]+a[i])%K;
	  L[i]=i-1;R[i]=i+1;
  }
  for(int i=0;i<=n;i++)S[s[i]].push_back(i),lis[i]=i;
  sort(lis+1,lis+n+1,comp);
  for(int i=1;i<=n;i++)a[lis[i]]%=K,solve(lis[i]);
  cout<<ans<<endl;
  return 0;
}

posted @ 2018-04-13 19:52  PIPIBoss  阅读(190)  评论(0编辑  收藏  举报