KDT 上打标记的 hot tea。
考虑将询问 \(A,B\) 看作二维平面直角坐标系上的一个点 \((A,B)\),那么我们这样考虑,我们从左到右扫过全部 \(n\) 个区间并开一个变量 \(cnt\) 表示当前与 \([l_i,r_i]\) 有交的区间的最大长度,如果当前扫描到的区间 \([l_i,r_i]\) 与区间 \([A,B]\) 有交,那么我们就令 \(cnt\) 加 \(1\),否则 \(cnt\) 清空,答案就是任意时刻最大的 \(cnt\)。
显然如果我们暴力地对所有区间都重复一遍这个操作那时间复杂度肯定爆炸,考虑如何优化,我们将所有询问离线下来并一遍扫过全部 \(n\) 个区间处理所有询问,不难发现对于一个区间 \([l_i,r_i]\) 而言,与其没有交的询问对应的点肯定会落在两个矩形内,因此我们每次需要做的即是:
最后要求的即是每个节点权值的历史最大值。
考虑对这 \(m\) 个询问点建立 KDT,那么根据 KDT 那一套理论,我们可以将矩形上的操作转化为 \(\sqrt{n}\) 个单点操作 \(+\) \(\sqrt{n}\) 个子树操作。单点操作是容易的,难点在于子树操作,因此我们现在要实现的即为:
对求解历史最大值线段树比较了解的同学到这一步应该会做了。但是我对理是最大值线段树不是太了解,因此这里会较为详细地讲解一下如何用历史最大值的标记解决这个问题。我们考察一个点上一次标记下推到现在一共经历了哪些过程,有两种可能,要么没有被清空,权值直接被加上了一个数 \(v\)。要么曾经被清空过,一开始先 \(+v_0\),然后清空,又加了 \(v_1\),再清空,\(+v_2\),清空,……,最后 \(+v_m\),值为 \(v_m\)。不难发现由于我们只关心历史最大值,因此 \(v_1,v_2,\cdots,v_m\) 具体是什么不重要,我们只用关心以下几个值:
当然除了这四个标记之外,还有每个位置的权值 \(v\) 以及历史最大值 \(hmx\)。
考虑对一个子树清空/整体加值会对标记产生怎样的影响。首先是子树清空,这个比较容易,直接将 \(clr\) 设为 \(1\),\(tg,v\) 设为 \(0\) 即可。其次是整体加值,如果 \(clr\ne 0\) 那就令 \(tg\) 加 \(v\),并将 \(cov\_mx\) 对 \(tg\) 取 \(\max\),否则令 \(add\_mx\) 加 \(v\)。
然后是下推标记的问题,如果 \(clr=0\) 那么直接对左右儿子进行整体 \(+add\_mx\) 操作即可。否则操作等价于先对左右儿子进行 \(+add\_mx\),然后 \(+v_1\),清空,\(+v_2\),清空,……,最后 \(+v_m\),这等价于先对左右儿子进行整体 \(+add\_mx\) 操作,然后令左右儿子的 \(cov\_mx\) 对该节点的 \(cov\_mx\) 取 \(\max\),然后令左右儿子的 \(tg\) 和 \(v\) 都等于 \(tg\)。注意每次操作之后都要实时更新 \(hmx\)。
时间复杂度 \(n\sqrt{m}\)。
const int MAXN=2e5;
const int K=2;
int n,m,res[MAXN+5];pii a[MAXN+5];
struct point{
int x[K+2],id;
point(){memset(x,0,sizeof(x));id=0;}
int& operator [](int id){return x[id];}
} p[MAXN+5];
struct node{int ch[2],v,hmx,add_mx,cov_mx,tg,clr;point val,mn,mx;} s[MAXN+5];
int ncnt=0,rt=0;
void build(int &k,int l,int r){
if(l>r) return;k=++ncnt;
static double avg[K+2],var[K+2];
fill(avg,avg+K,0);fill(var,var+K,0);
for(int i=l;i<=r;i++) for(int j=0;j<K;j++) avg[j]+=p[i][j];
for(int j=0;j<K;j++) avg[j]/=(r-l+1);
for(int i=l;i<=r;i++) for(int j=0;j<K;j++) var[j]+=(p[i][j]-avg[j])*(p[i][j]-avg[j]);
double mx=0;int dim=0;
for(int j=0;j<K;j++) if(mx<var[j]) mx=var[j],dim=j;
int mid=l+r>>1;
nth_element(p+l,p+mid,p+r+1,[&](point x,point y){return x[dim]<y[dim];});
build(s[k].ch[0],l,mid-1);build(s[k].ch[1],mid+1,r);
s[k].val=s[k].mn=s[k].mx=p[mid];
for(int i=0;i<K;i++){
if(s[k].ch[0]) chkmin(s[k].mn[i],s[s[k].ch[0]].mn[i]),chkmax(s[k].mx[i],s[s[k].ch[0]].mx[i]);
if(s[k].ch[1]) chkmin(s[k].mn[i],s[s[k].ch[1]].mn[i]),chkmax(s[k].mx[i],s[s[k].ch[1]].mx[i]);
}
}
void tagclr(int k){s[k].clr=1;s[k].tg=0;s[k].v=0;}
void tagadd(int k,int v){
s[k].v+=v;chkmax(s[k].hmx,s[k].v);s[k].tg+=v;
if(s[k].clr) chkmax(s[k].cov_mx,s[k].tg);
else chkmax(s[k].add_mx,s[k].tg);
}
void pushdown(int k){
if(s[k].add_mx){
if(s[k].ch[0]) tagadd(s[k].ch[0],s[k].add_mx);
if(s[k].ch[1]) tagadd(s[k].ch[1],s[k].add_mx);
s[k].add_mx=0;
} if(s[k].clr){
if(s[k].ch[0]){
s[s[k].ch[0]].clr=1;
chkmax(s[s[k].ch[0]].cov_mx,s[k].cov_mx);
chkmax(s[s[k].ch[0]].hmx,s[k].cov_mx);
s[s[k].ch[0]].tg=s[k].tg;
s[s[k].ch[0]].v=s[k].tg;
} if(s[k].ch[1]){
s[s[k].ch[1]].clr=1;
chkmax(s[s[k].ch[1]].cov_mx,s[k].cov_mx);
chkmax(s[s[k].ch[1]].hmx,s[k].cov_mx);
s[s[k].ch[1]].tg=s[k].tg;
s[s[k].ch[1]].v=s[k].tg;
} s[k].cov_mx=s[k].clr=0;
} s[k].tg=0;
}
void clrpt(int k){pushdown(k);s[k].v=0;}
void addpt(int k,int v){pushdown(k);s[k].v+=v;chkmax(s[k].hmx,s[k].v);}
void modify(int k,int l,int r){
if(!k) return;
if(s[k].mn[0]>r||s[k].mx[1]<l) return tagclr(k),void();
if(l<=s[k].mn[1]&&s[k].mx[0]<=r) return tagadd(k,1),void();
pushdown(k);
if(s[k].val[0]>r||s[k].val[1]<l) clrpt(k);
else addpt(k,1);
modify(s[k].ch[0],l,r);modify(s[k].ch[1],l,r);
}
void dfs(int k){
if(!k) return;res[s[k].val.id]=s[k].hmx;
pushdown(k);dfs(s[k].ch[0]);dfs(s[k].ch[1]);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d",&a[i].fi,&a[i].se);
for(int i=1;i<=m;i++) scanf("%d%d",&p[i][0],&p[i][1]),p[i].id=i;
build(rt,1,m);
for(int i=1;i<=n;i++) modify(rt,a[i].fi,a[i].se);
dfs(rt);
for(int i=1;i<=m;i++) printf("%d\n",res[i]);
return 0;
}
/*
8 2
4 5
5 6
2 4
1 3
5 7
6 7
1 6
2 5
4 7
4 5
*/
手机扫一扫
移动阅读更方便
你可能感兴趣的文章