好题集 (6) - BZOJ 4358 / MZOJ 703 permu
题意:给定一 \(1\sim n\) 的排列 \(a\) 和 \(m\) 次询问,每次询问区间 \([l,r]\) 内最长值域连续段的长度。\(n,m \le 5\times 10^4\)。
题目不强制在线,又看到这种诡异的区间询问,果断考虑莫队。
容易想到一种做法:外层用莫队转移至目标区间,内层使用线段树维护值域。线段树上每个结点维护从左端开始的最长值域段长度、到右端结束的最长值域段长度、整个结点内的最长值域连续段长度、左端点也没有值、右端点有没有值和结点长度,插入 / 删除时将对应链上的的结点信息依次更新、合并即可。
分析一下复杂度。由于使用线段树维护,我们的端点移动(也即插入 / 删除)复杂度是 \(O(\log n)\);而根结点是维护好的,所以单次查询复杂度是 \(O(1)\)。总复杂度 \(O(n\sqrt{n}\log n)\),理论可过;但由于常数太大,被卡掉了。但好像有大神用这个做法卡过去了?
思考这个做法劣在哪里。我们发现,端点会移动 \(O(n\sqrt{n})\) 次,而查询只有 \(O(n)\) 次。因此端点移动太慢,而询问没必要这么快。
考虑在这一思路的基础上,如何平衡复杂度。联想到把值域线段树换成值域分块,但这样复杂度会直接退化至 \(O(n^2)\),更劣了。而似乎不存在第三种数据结构可以维护这个东西。显然这个复杂度已经不太能平衡了。
于是我们放弃这个思路,考虑别的做法。
换一种思路,把值域上连续的一段看做一个集合,把插入新数看做集合的合并,把删除已有的数看做集合的分裂,答案即为所有集合中最大的大小。以下记 \(S_x\) 为 \(x\) 所在的集合。
那么,初始时没有两个及以上个数构成的值域连续段,所以 \(\forall S_x=\{x\}\)。当我们插入新值 \(v\) 时,需要检查 \(v-1\) 是否已经存在,如果存在则合并 \(S_{v-1}\) 和 \(S_v\),对应更新 \(|S_v|\) 和 \(|S_{v-1}|\);对于 \(v+1\) 也同理。
我们发现,基于这个思想,我们可以很容易地插入新数并更新 \(\max |S_x|\),但删除不是很好做。
于是想到不做删除,将外层的普通莫队替换为只加不减的回滚莫队。
左端点每进入一个新的块,就将所有集合还原到初始状态,答案设为 \(1\)。随后逐步扩展区间至目标位置,回滚时撤销左端点移动带来的集合合并和答案更改。
那么我们的集合就需要支持合并和撤销操作。使用并查集实现,那么合并是平凡的;考虑撤销操作。
不难想到对于整个集合维护一个 vector 表示都有哪些数被合并进来。回滚时顺着这个 vector 逐步还原 fa 数组即可。显然单次还原是 \(O(1)\) 的,且每轮回滚至多还原 \(O(\sqrt{n})\) 次。注意此时不能路径压缩,因为这会使得树结构改变,进而影响撤销。于是为了压缩复杂度,我们使用启发式合并,将合并复杂度控制在 \(O(\log n)\) 以内。
最后考虑左右端点在同一块的暴力怎么写。我们把对应区间复制到一个单独的数组(称为 \(b\))中以便操作。显然将 \(b\) 数组排序后值域上相邻的数在下标上也相邻,且答案不会改变。接下来考虑如何在排序后的 \(b\) 数组上求出答案。容易发现一个 DP 做法:设 \(f_i\) 表示以下标 \(i\) 结尾的最长值域连续段的长度,显然有转移:
答案即为 \(\max f_i\)。这部分的复杂度是 \(O(\sqrt{n}\log \sqrt{n})\),瓶颈在于排序。
那么完整做法就已经形成了:使用回滚莫队套可撤销并查集,每次将最大的集合大小作为答案。总复杂度 \(O(n\sqrt{n}\log n)\),看起来没有变,但后面的 \(\log\) 不太容易跑满,几乎就是一个比较大的常数。
代码:
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#include<iostream>
#include<cmath>
#include<vector>
#include<bitset>
#include<algorithm>
using namespace std;
const int N=1e5+5;
const int M=1e3+5;
int n,m;
int a[N];
namespace OIfast{
char buf[1<<21],*p1,*p2,*top,buffer[1<<21];
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?0:*p1++)
#define gc getchar()
inline int read(){
int n=0,f=1;static char c=gc;
while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
while(isdigit(c))n=(n<<3)+(n<<1)+(c^48),c=gc;
return n*f;
}
}using namespace OIfast;
inline void getmx(int &a,int b){
return a=(a>b?a:b),void();
}
namespace DSU{
vector<int>st;
int fa[N],sz[N];
inline void init_(){
for(int i=1;i<=n;++i)fa[i]=i,sz[i]=1;
return ;
}
inline int find(int x){
return x==fa[x]?x:find(fa[x]);
}
inline void merge(int x,int y){
x=find(x),y=find(y);
if(sz[x]>sz[y])swap(x,y);
fa[x]=y,sz[y]+=sz[x],st.push_back(x);
return ;
}
inline void rollback(int btm){
while(st.size()>btm){
int u=st.back();st.pop_back();
sz[fa[u]]-=sz[u],fa[u]=u;
}
return ;
}
}using namespace DSU;
namespace MD{
#define ql qu[i].l
#define qr qu[i].r
#define qid qu[i].id
int res;
int k,tot;
bitset<N>vis;
int ans[N];
int L[M],R[M];
short loc[N];
int b[N],f[N];
inline void init(){
k=320,tot=ceil((1.0*n)/(1.0*k));
for(int i=1;i<=tot;++i){
L[i]=(i-1)*k+1,R[i]=min(n,L[i]+k-1);
for(int j=L[i];j<=R[i];++j)loc[j]=i;
}
return ;
}
inline int calc(int l,int r,int u=1){
int idx=0;for(int i=l;i<=r;++i)b[++idx]=a[i],f[idx]=1;
sort(b+1,b+idx+1);
int tmp=1;for(int i=2;i<=idx;++i)if(b[i]==b[i-1]+1)f[i]=f[i-1]+1,getmx(tmp,f[i]);
return tmp;
}
inline void add(int v){
vis[v]=1;
if(vis[v-1])merge(v,v-1);
if(vis[v+1])merge(v,v+1);
return getmx(res,sz[find(v)]),void();
}
struct node{
int l,r,id;
}qu[N];
}using namespace MD;
signed main(){
// freopen("in.txt","r",stdin),freopen("out.txt","w",stdout);
n=read(),m=read();init();
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=m;++i)ql=read(),qr=read(),qid=i;
sort(qu+1,qu+m+1,[](node a,node b){return (loc[a.l]^loc[b.l])?(a.l<b.l):(a.r<b.r);});
int l=114,r=514;
for(int j=1,i=1;j<=tot;++j){
res=1,vis=0;
l=R[loc[ql]]+1,r=R[loc[ql]];
init_();
while(loc[ql]==j&&i<=m){
if(loc[ql]==loc[qr]){
ans[qid]=calc(ql,qr);
++i;
continue ;
}
while(r<qr)add(a[++r]);
int tmp=res,btm=st.size();
while(l>ql)add(a[--l]);
rollback(btm),ans[qid]=res,res=tmp;
while(l<R[loc[ql]]+1)vis[a[l++]]=0;
++i;
}
}
for(int i=1;i<=m;++i)printf("%d\n",ans[i]);
return 0;
}
提交记录。

浙公网安备 33010602011771号