[APIO2023] 序列 / sequence
等一分钟
以下方括号表示艾弗森括号,括号里的内容为真则括号的值是 \(1\),否则是 \(0\)。
题意
记 \(W(x, l, r)=\sum_{i=l}^r[a_i=x]\),求 \(\underset{1\leq l\leq r\leq n, v\text{是}a_l, a_{l+1}, ......, a_{r}\text{的中位数之一}}{\max}W(v, l, r)\)。
解法
显然的思路是枚举 \(v\),计算当 \(v\) 做中位数时,区间中最多可以包含多少 \(v\)。
经典思路是定义函数 \(f(x)=-[a_x<v]+[a_x>v], c(x)=[a_x=v]\) 于是有:
此时我的错误思路
考虑定义前缀和函数 $s(x)=\sum_{i=1}^xf(i), b(x)=\sum_{i=1}^xc(i)$。 那么问题等价于 $s(r)+b(r)\geq s(l-1)+b(l-1)$ 并且 $s(r)-b(r)\geq s(l-1)-b(l-1)$如果 \(i\) 对应一个点 \((s(i)+b(i), s(i)-b(i))\),那么问题相当于二维偏序。
当我们从小到大枚举 \(v\) 时,可以使用线段树维护 \(s\) 和 \(b\),但寻找编号上跨过最多 \(v\) 的偏序点对在横纵坐标都会变化的前提下是不可能完成的,于是我就不会了。
究其原因,在于 \(s+b\) 和 \(s-b\) 并不是任意的函数,而是具有特殊的性质。而在使用了一系列代数转化后,得到的点的横纵坐标就失去了实际意义而只剩下了代数意义。解决问题的办法是从实际意义失去前的步骤出发,保留实际意义进行推导。
现在假设 \(p_1<p_2< ......<p_k\) 是满足 \(a_i=v\) 的全体 \(k\) 个 \(i\)。那么我们需要对于所有 \(1\leq i\leq k\),找到最大的 \(i\leq j\leq k, s.t.\exists l\leq i, r\geq j, \text{使得}l, r\) 满足命题 \(P\)。
考虑二分 \(j\)。现在问题转化为判断是否存在 \(l\leq i, r\geq j\) 使得它们满足命题 \(P\)。
如果 \(P(i, j)\) 成立,那么这样的 \(l, r\) 自然成立。
反之,不妨设 \(\sum_{i=l}^rc(i)-\sum_{i=l}^rf(i)< 0\)(另一个不等式成立是同理的),那么为此需要通过扩展区间来设法降低 \(\sum_{i=l}^rf(i)\),具体来说,只需要找到 \(l\) 使得 \(\sum_{i=l}^{i-1}f(i)\) 最小,再找到 \(r\) 使得 \(\sum_{i=j+1}^{r}f(i)\) 最小,如果这样的 \(l, r\) 满足 \(\sum_{i=l}^rc(i)-\sum_{i=l}^rf(i)\geq 0\),那么可以断言原问题的答案是肯定的。
证明
这是因为把区间 \([i, j]\) 扩展到 \([l, r]\) 的过程中,第一个不等式一定在某一时刻(设从时刻 \(t\) 到时刻 \(t+1\))从不成立变为成立,而又因为两个不等式中至少成立一个,所以时刻 \(t\) 时第二个不等式成立。因为 \(\sum_{i=l}^rc(i)-\sum_{i=l}^rf(i)\) 每次扩展都只会变化 \(+1/-1\),所以在 \(t\) 时刻 \(\sum_{i=l}^rc(i)-\sum_{i=l}^rf(i)=-1, \sum_{i=l}^rc(i)+\sum_{i=l}^rf(i)\geq 0\),所以 \(\sum_{i=l}^rc(i)+\sum_{i=l}^rf(i)\geq 1\)(两式奇偶性相同,第一个式子是奇数,那么第二个式子也一定是奇数),所以 \(t+1\) 时刻第二个不等式必然成立,所以 \(t+1\) 时刻就是一个符合要求的时刻。
回到原题,只需要从小到大枚举 \(v\),用线段树维护 \(f\) 的前缀和/后缀和数组,然后枚举所有的 \(1\leq i\leq k\),二分 \(j\),使用之前讲的办法判断是否可行即可。时间复杂度 \(O(n\log^2n)\)。
代码
本题略有卡常,建议使用 zkw 线段树。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, b) for(ll i=a;i<=b;i++)
#define rrep(i, a, b) for(ll i=a;i>=b;i--)
const ll N=5e5+9, INF=1e18;
ll n, a[N];
vector <ll> pos[N];
struct segment_tree{
struct point{//区间加和查询最小/最大值
ll mx, mn, l,r,ad;
}tr[N*4];
ll leaf[N];
void buildtree(ll u, ll l, ll r){
tr[u].l=l, tr[u].r=r;
tr[u].mx=0;tr[u].mn=0;
if(l==r){leaf[l]=u;return;}
ll mid=(l+r)>>1;
buildtree(u<<1, l, mid);
buildtree(u<<1|1, mid+1, r);
}
void pushdown(ll u){
if(tr[u].ad){
tr[u<<1].ad+=tr[u].ad;
tr[u<<1|1].ad+=tr[u].ad;
tr[u<<1].mn+=tr[u].ad;
tr[u<<1|1].mn+=tr[u].ad;
tr[u<<1].mx+=tr[u].ad;
tr[u<<1|1].mx+=tr[u].ad;
tr[u].ad=0;
}
}
void add(ll u, ll l, ll r, ll ad){
if(l<=tr[u].l&&tr[u].r<=r){tr[u].ad+=ad;tr[u].mn+=ad;tr[u].mx+=ad;return;}
pushdown(u);
ll mid=(tr[u].l+tr[u].r)>>1;
if(l<=mid)add(u<<1, l, r, ad);
if(r>mid)add(u<<1|1, l, r, ad);
tr[u].mn=min(tr[u<<1].mn, tr[u<<1|1].mn);
tr[u].mx=max(tr[u<<1].mx, tr[u<<1|1].mx);
}
ll querypoint(ll x){
ll u=leaf[x], res=0;
while(u){res+=tr[u].ad;u>>=1;}
return res;
}
ll querypremi(ll x){
ll u=leaf[x], res=tr[u].mn-tr[u].ad;
while(u){
res+=tr[u].ad;
if((u&1)&&u>1){
res=min(res, tr[u-1].mn);
}
u>>=1;
}
return res;
}
ll querylasma(ll x){
ll u=leaf[x], res=tr[u].mx-tr[u].ad;
while(u){
res+=tr[u].ad;
if((u&1)==0){
res=max(res, tr[u+1].mx);
}
u>>=1;
}
return res;
}
}tr1, tr2;
//tr1的差分数组是[ai<=v]1+[ai>v](-1),tr2的差分数组是[ai>=v]1+[ai<v](-1)
bool OK(ll i, ll j){
//检验能否找到一个包含[i, j]的合法区间(合法区间要求tr1[r]-tr1[l-1]>=0, tr2[r]-tr2[l-1]>=0)
if(tr1.querypoint(j)-tr1.querypoint(i-1)<0){
return tr1.querylasma(j)-tr1.querypremi(i-1)>=0;
}else if(tr2.querypoint(j)-tr2.querypoint(i-1)<0){
return tr2.querylasma(j)-tr2.querypremi(i-1)>=0;
}else{
return 1;
}
}
int sequence(int N, std::vector<int> A){
n=N;
rep(i, 1, n)a[i]=A[i-1];
rep(i, 1, n){
pos[a[i]].push_back(i);
}
tr1.buildtree(1, 0, n);
tr2.buildtree(1, 0, n);
rep(i, 1, n){
tr1.add(1, i, n, -1);
tr2.add(1, i, n, 1);
}
ll res=0;
rep(v, 1, n){
// if(pos[v].empty())continue;
for(ll t:pos[v-1]){
tr2.add(1, t, n, -2);
}
for(ll t:pos[v]){
tr1.add(1, t, n, 2);
}
for(ll i=0;i<pos[v].size();i++){
ll l=i+1, r=pos[v].size()-1, ans=i;
while(l<=r){
ll mid=(l+r)>>1;
if(OK(pos[v][i], pos[v][mid])){
ans=mid;
l=mid+1;
}else{
r=mid-1;
}
}
res=max(res, ans-i+1);
}
}
return res;
}
总结
这道题的关键在于找到快速且具有扩展性的中位数检验方法。我在“我的错误想法”中给出的方法同样能够快速检验 \(v\) 是否是某个区间的中位数,但它不具有扩展性,原因是它的形式是任意两个数列,不具有实际意义。而题解给的做法具有鲜明的实际意义,从中我们得到了如下的关键性质:
- 两个不等式最多只有一个不成立
- 两个式子奇偶性相同
- 两个式子都满足区间左端点/右端点扩张 \(1\) 时,只变化 \(+1/-1\)。
这三个通过实际意义观察出的性质是解题的关键。

浙公网安备 33010602011771号