考虑数字x的贡献
设S[i]表示x在前i个位置的出现次数
那么如果存在区间[l+1,r]满足要求,则S[r]-S[l]>(r-l)/2,即2S[r]-r>2S[l]-l
令T[i]=2S[i]-i
那可以得到一个n^2 * logn的做法:
枚举数字x,枚举位置,用一个数据结构统计一段区间内<某个数的个数
优化一下这个做法
对于某个数字x,看一下它的T
例如下例中,x=2
位置:0 1 2 3 4 5 6 7 8 9 10 11
数字: 1 2 5 3 2 3 1 2 4 2 1
T : 0 -1 0 -1 -2 -1 -2 -3 -2 -3 -2 -3
段: 1 1 2 2 2 3 3 3 4 4 5 5
我们发现如果存在k个x,那么他把整个序列分为了k+1段,如上所示
每一段是一个等差数列
用a[i]表示T为i的数的出现次数
对于某个x的所有段,按从左到右依次处理
假设现在这一段的T值范围为[l0,r0]
那么以这一段为右端点的区间对答案的贡献就是
式子里j写负无穷表示一个足够小的边界
然后对a中区间[l0,r0]的数加1
我们看用什么样的数据结构能够快速完成查询和修改的操作
查询里有2个∑,我们用前缀和去掉一个,用b[i]表示a的前缀和
每一段的贡献就是,即区间求和
那么修改操作对a中区间[l0,r0]的数加1,就是对b中区间[l0,r0]的数加首项为1,公差为1的等差数列
这可以用线段树来完成
时间复杂度nlogn
继续想,用c[i]表示b的前缀和,即a的二阶前缀和
那么每一段的贡献就是c[r0-1] - c[l0-2]
区间加操作怎么做?
区间加一个数可以想到利用差分数组变为单点修改
令d表示a的差分数组
现在a b c d的关系是 d是a的差分数组,a是d的前缀和,b是a的前缀和即d的二阶前缀和,c是b的前缀和即d的三阶前缀和
把所有的下标都右移n+1位,变为正整数
现在可以用树状数组维护d[k] , k*d[k] , k*k*d[k] 的前缀和
#include<bits/stdc++.h> using namespace std; #define N 500003 int nn; long long c0[N<<1],c1[N<<1],c2[N<<1]; vector<int>v[N]; int a[N],sum[N]; #define lowbit(x) x&-x long long query(int x) { long long ss=0; for(int i=x;i;i-=lowbit(i)) ss+=1ll*(x+1)*(x+2)*c0[i]-(2ll*x+3)*c1[i]+c2[i]; return ss>>1; } void change(int x,int k) { for(int i=x;i<=nn;i+=lowbit(i)) { c0[i]+=k; c1[i]+=k*x; c2[i]+=1ll*k*x*x; } } int main() { int n,x; long long ans=0; scanf("%d%*d",&n); for(int i=1;i<=n;++i) { scanf("%d",&x); v[x].push_back(i); } int delta=n+1,y,siz,last; nn=2*n+1; for(int i=0;i<n;++i) { v[i].push_back(n+1); siz=v[i].size(); last=0; for(int j=0;j<siz;++j) { x=2*j-last+delta; y=2*j-(v[i][j]-1)+delta; ans+=query(x-1); if(y>2) ans-=query(y-2); change(y,1); change(x+1,-1); last=v[i][j]; } last=0; for(int j=0;j<siz;++j) { x=2*j-last+delta; y=2*j-(v[i][j]-1)+delta; change(y,-1); change(x+1,1); last=v[i][j]; } } printf("%lld ",ans); }