P4062 [Code+#1]Yazid 的新生舞会
题意
给定一个长度为 \(n\) 的序列 \(A\)。求有多少个子区间满足存在一个数,在该区间内出现的次数超过区间一半。
Solution
考虑转化题意,如果我们尝试枚举这个出现次数超过一半的数字是 \(v\),则我们将 \(v\) 令作 \(1\),不等于 \(v\) 令作 \(-1\)。那么对于当前以 \(v\) 为超过一半的数字的子区间应当满足区间和大于 \(0\)。
现在假如说我们已经得到了这个 \(1,-1\) 的序列,那么发现还是很难用某种 DS 来维护。
于是考虑前缀和,那么就转化为顺序对的个数。这样用树状数组很好做对吧。
接下来考虑一个困难一点的问题,就是你不可能枚举每一个数字都重新跑一次,这样是 \(O(n^2\log n)\) 的。所以需要有效利用此前已经有的信息。
每一次的修改相当于把所有的 \(-1\) 改成 \(1\),并把某些位置改成 \(-1\)。不难想到这是一个后缀加减的问题。
看了眼题解,其实不需要利用之前的信息(因为确实也不好利用)。注意到,每次枚举到一个数的时候,假设这个数出现了 \(cnt\) 次,那相当于是在一个初始为全 \(0\) 的数组上的 \(cnt\) 个位置设为 \(1\),然后剩下的最多 \(cnt+1\) 段设为 \(-1\)。假设我们非常高明地能够在 \(O(\log n)\) 的时间内单点插入 \(1\) 和区间插入 \(-1\),那么总的时间复杂度其实还是 \(O(n\log n)\),因为 \(\sum cnt=n\)。
好那我们考虑这个高明的操作是什么。如果说加一段 \(-1\),那前缀和之后相当于是加上了一串等差数列。那这个操作相当于是在值域线段树上区间加。
好现在考虑统计答案,首先很显然的是如果是单点插入的话可以直接查询线段树上前缀和就行了。但是现在你需要插入一段连续的序列,怎么得到这一段的贡献呢?
我们发现这一段 \([l,r]\) 插入的贡献是 \(\sum_{i=l}^r\sum_{j=0}^{i-1}sum_j\)。然后每次统计完后再将序列加入线段树中。我们令 \(pre_i\) 表示线段树上前 \(i\) 位置的 \(sum\) 之和。那么贡献就变成了 \(\sum_{i=l}^rpre_{i-1}\)。仔细一想,这玩意儿好像还能用前缀和来优化,即前缀和的前缀和。
那么我们就完全明白了,既然只是前缀和,那不如用树状数组来得简单自然。那我们在树状数组上维护单点的 \(1,-1\) 数组的前缀和的值域,然后维护前缀和的前缀和,再维护前缀和的前缀和的前缀和就可以了:)
具体维护可以借鉴树状数组区间修改的做法。我们记其单点值为 \(c_1\),\(c_1\) 的前缀和是 \(c_2\),\(c_2\) 的前缀和是 \(c_3\)。那么区间加一的时候,利用其差分,我们在 \(l\) 加一,并在 \(r+1\) 减一,假设这样维护出来的数组是 \(d\),那么有:
那么的话,你在树状数组上维护 \(i^2d\),\(id\),\(d\) 就可以了。
Code
const int MAXN=1e6+10;
int d1[MAXN],d2[MAXN],d[MAXN],N;
int lbt(int x){return x&(-x);}
void upd(int x,int v){
for(int i=x;i<=N;i+=lbt(i))
d[i]+=v,d1[i]+=v*x,d2[i]+=v*x*x;
}
int ask(int x){
if(x<=0) return 0;
int ret=0;
for(int i=x;i;i-=lbt(i))
ret+=d2[i]-(3+2*x)*d1[i]+(x*x+3*x+2)*d[i];
return ret/2;
}
int a[MAXN];
vector<int> p[MAXN];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n,ZCETHAN;cin>>n>>ZCETHAN;
rep(i,1,n){cin>>a[i];p[a[i]].pb(i);}
rep(i,0,n-1) p[i].pb(n+1);
N=2*n+1;
int ans=0;
rep(i,0,n-1){
int l,r;
for(int j=0;j<(int)p[i].size();j++){
if(j==0) r=2*j+n+1;
else r=2*j-p[i][j-1]+n+1;
l=2*j-(p[i][j]-1)+n+1;
ans+=ask(r-1)-ask(l-2);
upd(l,1);upd(r+1,-1);
}for(int j=0;j<(int)p[i].size();j++){
if(j==0) r=2*j+n+1;
else r=2*j-p[i][j-1]+n+1;
l=2*j-(p[i][j]-1)+n+1;
upd(l,-1);upd(r+1,1);
}
}cout<<ans<<'\n';
return 0;
}