题解 P4062 【[Code+#1]Yazid 的新生舞会】
wurzang
2020-07-06 19:21:39
讲一个暴力点的做法。
首先发现如果一个数字是 $[l,r]$ 之间的众数的话,那么这个数字肯定是 $[l,mid]$ 之间的众数或者是 $[mid+1,r]$ 之间的众数。(其中 $mid=\left\lfloor\frac{l+r}{2}\right\rfloor$)
于是就可以分治,对于每一个区间 $[l,r]$ 计算出经过 $mid$ 的满足题意的区间数。
直接对于每个数字暴力计算是 $\mathcal{O}(n^2 \operatorname{log}^2 n)$ 的,肯定过不去,考虑优化。
可以考虑直接挑出可能成为经过 $mid$ 的区间的众数的数字。发现这个数字数量是 $\log n$ 级别的,于是优化到 $\mathcal{O}(n\operatorname{log}^3n)$
你可能觉得3只log不可能过5e5,但因为实际上压根跑不满,于是它过了,而且还跑得飞快,还跑得比 $\sf\color{black}{L}\color{red}{imit}$ 的线段树做法快...
至于接下来怎么去做,相信很多题解里已经讲过了,我这里再复述一遍。
挑出这些数字以后,我们把问题转化为:给定一个数字 $x$,求区间 $[l,r]$ 中有多少子区间经过 $mid$,而且满足区间众数是 $x$ 且区间满足题意。
根据摩尔投票法,我们可以把每一个等于 $x$ 的地方转为 1,否则就是 -1,问题变成了求有多少子区间经过 $mid$,而且满足区间和大于0。
考虑把区间 $[l,r]$ 前缀和一下,设前缀和后第 $i$ 个点的结果为 $sum_i$,问题变成了求
$$
\sum_{i=mid}^r \sum_{j=l}^{mid-1} [sum_i>sum_j]
$$
其中 $[p]$ 表示布尔表达式,即 $p$ 是否为真。
这坨东西发现可以轻松用树状数组搞定。然后就没有然后了。
不过发现区间大小为 1 的时候容易重复计算,那么就直接加个区间大小大于 1 的限制即可,即求:
$$
\sum_{i=mid+1}^r \sum_{j=l}^{mid-1} [sum_i>sum_j]
$$
最后加上大小为 1 的区间数量即可。时间复杂度 $\mathcal{O}(n\operatorname{log}^3n)$
代码如下:
```cpp
#include<bits/stdc++.h>
using namespace std;
const int N=500000+5;
int n,tree[N<<2];
int limit;
void add(int x,int c){
x+=n+1;
while(x<=limit) tree[x]+=c,x+=(x&-x);
}
int query(int x){
int res=0;x+=n+1;
while(x>0) res+=tree[x],x-=(x&-x);
return res;
}
long long ans;
int cnt[N],a[N];
set<int>::iterator it;
void calc(int x,int l,int mid,int r){
int sum=0;
add(0,1);
for(int i=l;i<=mid;++i){
sum+=(a[i]==x?1:-1);
if(i!=mid) add(sum,1);
//cout<<sum<<" A"<<endl;
}
for(int i=mid+1;i<=r;++i)
sum+=(a[i]==x?1:-1),
//cout<<sum<<" B\n",
ans+=query(sum-1);
sum=0;
for(int i=l;i<mid;++i){
sum+=(a[i]==x?1:-1);
add(sum,-1);
}
add(0,-1);
}
void query(int l,int r){
if(l==r) return ++ans,void(0);
int mid=(l+r)>>1;
query(l,mid);query(mid+1,r);
set<int> s;
for(int i=mid+1,mx=0;i<=r;++i){
++cnt[a[i]];
if(cnt[a[i]]>cnt[mx]) mx=a[i],s.insert(mx);
}
for(int i=mid+1;i<=r;++i) --cnt[a[i]];
for(int i=mid,mx=0;i>=l;--i){
++cnt[a[i]];
if(cnt[a[i]]>cnt[mx]) mx=a[i],s.insert(mx);
}
//out<<l<<" "<<r<<" seg\n";
//for(int i=l;i<=r;++i)cout<<a[i]<<" ";cout<<endl;
for(int i=mid;i>=l;--i) --cnt[a[i]];
for(it=s.begin();it!=s.end();++it)
//cout<<l<<" "<<r<<" "<<(*it)<<" "<<ans<<endl,
calc(*it,l,mid,r);
}
int main(){
int type;
scanf("%d%d",&n,&type);limit=n+n+1;
for(int i=1;i<=n;++i)
scanf("%d",&a[i]),a[i]=a[i]+1;
query(1,n);
cout<<ans;
return 0;
}
```