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;
}

原文地址:https://www.cnblogs.com/Yuzao/p/8503763.html