【Ynoi2008】rsmemq
根号分治。
题目大意:
给定一个长度为 $n$ 的序列 $a$。
$m$ 组询问,每次给出 $l,r$,问有多少 $l’,r’$ 满足 $l\leq l’\leq r’\leq r$ 且 $r’-l’+1$ 是偶数,且 $\frac {l’+r’}2$ 是 $a_{l’},a_{l’+1},a_{l’+2},\ldots,a_{r’}$ 的众数。
题解:
$5\times 10^5$ 已成为常见的根号复杂度范围.jpg
本题可以分为两个部分,下面分别对其分析。
第一部分(预处理)
我们对每个不同的数值进行考虑。考虑当前的数为 $v$,它的出现次数为 $c$。
由于 $v$ 能产生贡献的区间的中点位置必须是 $v$,因此我们考虑枚举两个端点到中点的距离 $L$。$v$ 在区间的出现次数显然随着 $L$ 的增大而单调不降,且出现次数为 $0\sim c$。
我们再对 $v$ 的每个不同的出现次数进行考虑。对于 $v$ 出现了 $i$ 次,它对应的 $L$ 是连续的,设为区间 $[l,r]$。我们可以找到一个 $m\in[l-1,r]$ 满足当 $L\in[l,m]$ 时,$v$ 是区间的众数,而当 $L\in[m+1,r]$ 时,$v$ 不是区间的众数。证明很简单,这里略去。
因此我们可以使用二分来找到这个 $m$ 的值,从而能得出所有能产生贡献的 $L$ 的范围。由于总共有 $c$ 个不同的出现次数($0$ 次不可能成为众数,故略去),因此可以得到 $c$ 个互不相交的范围。
由于二分的时候还需要求区间众数,因此对于一个 $v$,求它的 $L$ 的范围需要 $O(c\sqrt n\log n)$ 或 $O(c\sqrt{n\log n})$ 的时间复杂度。而所有的 $v$ 的 $c_v$ 的和为 $n$,因此这部分的总时间复杂度为 $O(n\sqrt n\log n)$ 或 $O(n\sqrt{n\log n})$。这个复杂度还不能接受。
考虑优化上述过程。
容易发现,$c_v\gt \sqrt n$ 的不同的 $v$ 最多有 $\sqrt n$ 个,这部分我们可以暴力枚举 $L$ 并进行判断,时间复杂度 $O(n\sqrt n)$(记得把连续的 $L$ 并成一个区间,否则空间复杂度将为 $O(n\sqrt n)$)。
而对于 $c_v\leq \sqrt n$ 的所有 $v$,我们不妨考虑枚举众数的出现次数。
假设当前众数的出现次数为 $(k-1)$,我们可以在 $O(n)$ 内求出 $p_{1..n}$,其中 $p_i$ 表示最小的满足 $[i,j]$ 的区间众数出现次数为 $k$ 的 $j$。
以下是求数组 $p$ 的方法:
可以证明 $a_{p_i}$ 是区间 $[i,p_i]$ 的唯一众数,否则 $[i,p_i-1]$ 的众数的出现次数也为 $k$。
同理,若 $a_i\neq a_{p_i}$,则 $[i+1,p_i]$ 的众数的出现次数也为 $k$。
我们对一个 $[i,p_i]$ 的左端点不断按此法向右移动,得到的 $[j,p_i]$ 会满足 $a_j=a_{p_i}$,即 $p_i$ 是位置 $j$ 之后第 $(k-1)$ 个 $a_j$。
考虑令 $q_i$ 表示 $i$ 之后第 $(k-1)$ 个 $a_i$ 的出现位置,那么对于一个 $i$,就有 $p_i=\min_{j>i} q_j$。
也就是说 $p_i$ 是 $q_i$ 的后缀 $\min$ 数组。
用 vector 按顺序存每个数值的出现位置,然后再对每个位置记一下它在 vector 里的下标,就可以 $O(1)$ 得到单个 $q_i$。
那么这部分的时间复杂度为 $O(n)$。
枚举可能的数值 $v$,并取出 $l,r$ 满足当且仅当 $L\in[l,r]$ 时,$v$ 的出现次数恰好为 $k$。
我们在上面提到了二分这个 $m$,在二分的时候,我们需要判断的其实是,是否存在一个区间 $[s,t]\in[v-m,v+m]$ 满足 $[s,t]$ 的区间众数大于等于 $k$。而我们只需要对所有的 $i\in[v-m,v+m]$ 检查:满足 $[i,j]$ 的众数出现次数恰好为 $k$ 的最小的 $j$ 是否大于 $v+m$,这个 $j$ 就是上面我们求出的 $p_i$。而由于 $p_i$ 具有单调性,因此我们只需要检查 $p_{v-m}$ 是否大于 $v+m$ 即可。因此单次检验是 $O(1)$ 的,一次二分的复杂度就是 $O(\log n)$。
由于所有的 $v$ 的 $c_v$ 总和为 $n$,因此这里的二分的次数不会超过 $n$ 次,时间复杂度为 $O(n\log n)$。
而我们需要对每个 $k\leq \sqrt n$ 计算对应的 $p$ 数组,因此这部分的时间复杂度是 $O(n\sqrt n)$。
第二部分(回答询问)。
我们接下来需要解决的问题如下:
给定不超过 $n$ 个三元组 $(v,x,y)$ 满足 $x\leq y$。对每个三元组,枚举 $i\in[x,y]$,并令 $A_{v-i,v+i}$ 增加 $1$。
$m$ 次询问,每次给定 $l,r$,求 $\sum\limits_{i=l}^r\sum\limits_{j=l}^r A_{i,j}$ 的值。
这个问题有多种做法,这里介绍一种较方便且容易理解的做法(感谢 memset0 提供了该做法的思路)。
上面这个二维的问题是比较容易想到的转化后的题意,不过我们还是直接讨论一个三元组 $(v,x,y)$ 对 $l,r$ 的贡献。
令 $a$ 为 $[v-y,v-x]\cap[l,r]$ 的长度,$b$ 为 $[v+x,v+y]\cap[l,r]$ 的长度,则贡献为 $\min(a,b)$。
令 $mid=\lfloor\frac{l+r}2\rfloor$,可以发现,当 $mid\leq v$ 时,总有 $a\geq b$,当 $mid\gt v$ 时,总有 $a\lt b$。
那么我们对 $mid\leq v$ 和 $mid\gt v$ 两种情况分开统计贡献即可。每种情况都是一个简单的二维数点问题,使用离线树状数组解决即可,时间复杂度 $O((n+m)\log n)$,空间复杂度 $O(n+m)$。
综上,算法的时间复杂度为 $O(n\sqrt n+m\log n)$,空间复杂度为 $O(n+m)$,可以通过本题。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,siz=450;
typedef long long LL;
int __MEMORY__[N],*__ITERATOR__=__MEMORY__;
class Vector{
int*__BEGIN__,*__END__,*__IT__;
public:
inline void INIT(int*a,int*b){__BEGIN__=__IT__=a,__END__=b;}
inline int*begin(){return __BEGIN__;}
inline int*end(){return __END__;}
inline int size(){return __IT__-__BEGIN__;}
inline int&operator[](const int&x){return __BEGIN__[x];}
inline void push_back(const int&x){*__IT__++=x;}
inline int&back(){return*__IT__;}
}pos[N];
int n,m,a[N],tot[N],rnk[N],q;
struct dat{
int m,l,r,I;
inline bool operator<(const dat&rhs)const{return m!=rhs.m?m<rhs.m:I<rhs.I;}
}D[2*N];
inline void add(int i,int l,int r,int tp=0){if(l<=r)D[++q]=(dat){i,l,r,tp};}
LL ans[N];
struct bit{
int B[N];LL C[N];
inline void add(int i,int x){for(int k=i;i<=n;i+=i&-i)B[i]+=x,C[i]+=(LL)k*x;}
inline LL ask(int i){LL x=0;for(int k=i;i;i&=i-1)x+=B[i]*(k+1LL)-C[i];return x;}
}b,c;
vector<dat>vec[siz+5];
void solve_leq_sqrt(){
for(int s=1;s<=siz;++s)if(vec[s].size()){
static int _r[N];
for(int i=1;i<=n;++i)if(rnk[i]+s<pos[a[i]].size())_r[i]=pos[a[i]][rnk[i]+s];else _r[i]=n+1;
for(int i=n-1;i;--i)_r[i]=min(_r[i],_r[i+1]);
for(dat it:vec[s]){
int l=it.l,r=it.r,m=it.m,res=l-1;
while(l<=r){
const int mid=(l+r)/2;
if(_r[m-mid]>m+mid)l=(res=mid)+1;else r=mid-1;
}
if(it.l<=res)add(m,it.l,res);
}
}
}
void solve(){
sort(D+1,D+q+1);
for(int i=1;i<=q;++i)if(D[i].I)ans[D[i].I]+=b.ask(D[i].r)-b.ask(D[i].l-1);
else b.add(D[i].m-D[i].r,1),b.add(D[i].m-D[i].l+1,-1);
for(int i=q;i;--i)if(D[i].I)ans[D[i].I]+=c.ask(D[i].r)-c.ask(D[i].l-1);
else c.add(D[i].m+D[i].l,1),c.add(D[i].m+D[i].r+1,-1);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)cin>>a[i],rnk[i]=+tot[a[i]]++;
#define it __ITERATOR__
for(int i=1;i<=n;++i)pos[i].INIT(it,it+tot[i]),it+=tot[i];
#undef it
for(int i=1;i<=n;++i)pos[a[i]].push_back(i);
for(int i=1;i<=n;++i)if(tot[i]>siz){
static int cnt[N];
memset(cnt,0,sizeof(int)*(n+1)),cnt[a[i]]=1;
if(a[i]==i)add(i,0,0);
int mx=1,pre=1;
for(int len=1;;++len){
int l=i-len,r=i+len;
if(l<1||r>n){add(i,pre,len-1);break;}
++cnt[a[l]],++cnt[a[r]];
mx=max({mx,cnt[a[l]],cnt[a[r]]});
if(cnt[i]!=mx)add(i,pre,len-1),pre=len+1;
}
}else if(tot[i]){
Vector&v=pos[i];
static int sta[N];int top=0,lm=min(n+1-i,i);
for(int j=0,ed=v.size();j<ed;++j){
int x=abs(i-v[j]);
if(x<lm)sta[++top]=x;
}
sort(sta+1,sta+top+1),sta[++top]=lm;
for(int j=1;j<top;++j){
int l=sta[j],r=sta[j+1]-1;
if(l<=r)vec[j].emplace_back((dat){i,l,r});
}
}
solve_leq_sqrt();
for(int i=1,l,r;i<=m;++i)cin>>l>>r,add((l+r)/2,l,r,i);
solve();
for(int i=1;i<=m;++i)cout<<ans[i]<<'\n';
return 0;
}