主席树题集及小结

怕期末月完了就忘完了,蒟蒻总结一波。


先来模板题:求静态区间第k小

https://www.luogu.com.cn/problem/P3834

对于板子的理解:

板子的博客

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
LL a[maxn],b[maxn],root[maxn];
LL tot=0;
struct Tree{
    LL l,r,rt,sum;
}tree[maxn*20];
LL update(LL pre,LL l,LL r,LL x){
    LL rt=++tot;
    tree[rt].l=tree[pre].l;
    tree[rt].r=tree[pre].r;
    tree[rt].sum=tree[pre].sum+1;
    LL mid=(l+r)>>1;
    if(l<r){
        if(x<=mid){
            tree[rt].l=update(tree[pre].l,l,mid,x);
        }
        else tree[rt].r=update(tree[pre].r,mid+1,r,x);
    }
    return rt;
}
LL query(LL v,LL u,LL l,LL r,LL k){
    if(l==r) return l;
    LL x=tree[tree[u].l].sum-tree[tree[v].l].sum;
    LL mid=(l+r)>>1;
    if(x>=k){
        return query(tree[v].l,tree[u].l,l,mid,k);
    }
    else return query(tree[v].r,tree[u].r,mid+1,r,k-x);
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n,m;cin>>n>>m;
  for(LL i=1;i<=n;i++){
      cin>>a[i];
      b[i]=a[i];
  }
  sort(b+1,b+1+n);
  LL siz=unique(b+1,b+1+n)-b-1;///返回不同的数字的个数
  for(LL i=1;i<=n;i++){
    LL x=lower_bound(b+1,b+1+siz,a[i])-b;
    root[i]=update(root[i-1],1,siz,x);
  }
  while(m--){
    LL l,r,k;cin>>l>>r>>k;
    LL t=query(root[l-1],root[r],1,siz,k);
    cout<<b[t]<<endl;
  }
return 0;
}

以下是静态区间题的应用:

一.求树上的某条路径上第k小的点(LCA+主席树)

Count on a tree

 SPOJ - COT  

详情


二.求树中两点路径的不同颜色数量(树上莫队)

Count on a tree II

SPOJ - COT2 

详解:这里


三.区间内小于等于k的数字有多少)[主席树+二分]

详情


四.主席树维护线段树区间修改(标记永久化)

To the moon

 HDU - 4348 

思路:


来自Lautisticyc

我们就一直把懒标记留在1和5两个点上,不用下传,查询的时候再加。

比如我们要查询6节点的值。

先从1节点开始,向右边走,顺便加上它的懒标记33。

到了5节点,又往左走,也要加上懒标记55。

最后到了6节点,得到它的现在值就是它的原值加上刚刚加上的懒标记的和。

区间查询的话,懒标记加上时要乘上区间元素个数。(见懒标记定义)


个人理解:其实过程是这样的,为了防止主席树的lazy标记下传影响共用节点,于是有了标记永久化。

也就是对于一个区间,我们只维护lazy而不下传,但是注意我们维护push_up;

在update中,对要进行修改的区间进行打tag,打完先不更新这个点的和,打完后push_up(当前父亲的区间和不仅由左右儿子相加,还有左右儿子的tag*左右儿子对应的区间)。

对于下一棵树的建树,在前一棵上先复制。到了这一颗树要打tag的时候,我们将这个点tag+=这次打的标记(因为之前的我们复制了)打完了之后然后同理push_up上去。

对于查询,我们在查询的过程中注意累加当前树区间的tag对于结果区间的影响,也就是LL cnt=tree[now].add*(QR-QL+1);

对于这种查询,用三种区间的查询容易操作,分别是全在左子树,全在右子树,和跨越区间。注意跨越区间的时候要将查询的区间修改了(因为我们的QR-QL+1的大小是要变化的,不然每次都乘一个最开始的大区间了)

找到了对应的完全覆盖区间注意别忘了加上这个区间的add*(这个区间的长度)。然后累加

代码比较好理解

注意如果是hdu题的话,这题要多组,同时对sum有关的都开long long.洛谷不用多组。

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+50;
typedef int LL;
inline LL read()
{	LL x=0,f=1;char ch=getchar();	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
LL root[maxn],tot=0;
struct Tree{
    LL lson,rson,add;long long sum;
   /// LL l,r;///节点代表的左右区间
}tree[maxn<<5];
LL a[maxn];
LL build(LL l,LL r){
   LL rt=++tot;
   ///tree[rt].l=l;tree[rt].r=r;
   if(l==r) {
     tree[rt].sum=a[l];
     return rt;
   }
   LL mid=(l+r)>>1;
   tree[rt].lson=build(l,mid);
   tree[rt].rson=build(mid+1,r);
   tree[rt].sum=tree[tree[rt].lson].sum+tree[tree[rt].rson].sum;
   return rt;
}
LL update(LL pre,LL l,LL r,LL ql,LL qr,long long val){
    LL rt=++tot;
    tree[rt]=tree[pre];
    if(ql<=l&&qr>=r){
        tree[rt].add+=val;///打lazy标记
        ///tree[rt].sum+=val*(tree[rt].r-tree[rt].l+1);不能先加
        return rt;
    }
    LL mid=(l+r)>>1;
    if(ql<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,ql,qr,val);
    }
    if(qr>mid){
        tree[rt].rson=update(tree[pre].rson,mid+1,r,ql,qr,val);
    }
    LL add1=tree[tree[rt].lson].add;
    LL add2=tree[tree[rt].rson].add;
    tree[rt].sum=tree[tree[rt].lson].sum+tree[tree[rt].rson].sum+add1*(mid-l+1)+add2*(r-(mid+1)+1);
    return rt;
}
long long query(LL now,LL l,LL r,LL ql,LL qr){
    if(ql<=l&&qr>=r){
        return tree[now].sum+tree[now].add*(r-l+1);
    }
    long long cnt=tree[now].add*(qr-ql+1);
    LL mid=(l+r)>>1;
    if(qr<=mid) return cnt+query(tree[now].lson,l,mid,ql,qr);
    else if(ql>mid) return cnt+query(tree[now].rson,mid+1,r,ql,qr);
    else{
        return cnt+query(tree[now].lson,l,mid,ql,mid)+query(tree[now].rson,mid+1,r,mid+1,qr);///注意由于标记永久化,这里往下搜的ql,qr要变(因为lazy乘要乘长度)
    }
}
int main(void)
{
  char op[10];
  LL n,m;
  while(~scanf("%d%d",&n,&m)){
    LL TIME=0;
    LL tot=0;
    for(LL i=1;i<=n;i++){
        a[i]=read();
    }
    root[0]=build(1,n);
    for(LL i=1;i<=m;i++){
        scanf("%s",op);
        if(op[0]=='C'){LL l,r,d;l=read();r=read();d=read();TIME++;root[TIME]=update(root[TIME-1],1,n,l,r,d); }
        if(op[0]=='Q'){LL l,r;l=read();r=read(); long long ans=query(root[TIME],1,n,l,r);printf("%lld\n",ans); }
        if(op[0]=='H'){LL l,r,t;l=read();r=read();t=read();long long ans=query(root[t],1,n,l,r);printf("%lld\n",ans); }
        if(op[0]=='B'){LL t;t=read();TIME=t;}
    }

  }
return 0;
}

五.求区间内不同数字的个数

这个问题其实可以莫队解决,但是如果卡nsqrt(n)呢?还是有必要掌握的。

P1972 [SDOI2009]HH的项链(这题我看最近有人莫队卡过去了,反正我没卡过去。好像主席树这题也能卡)

D-query

 SPOJ - DQUERY

思路:如果权值比较大要进行离散化。

扫描序列建立可持续化线段树。

对每个pos建立主席树。pos对应单点修改的位置,找到+=val;

如果未出现,就赋值当前位置去找。不然先在root[i-1]基础上减去上次这个数字的贡献,再加这次这个数字的贡献。

同时维护每一个元素上一次出现的位置。

query的时候:每一个主席树维护的是1~i(当前位置)的不同数字有多少个。

找到时候在root【r】中找,树中区间编号>=l的,就是1-r中,l~r的贡献所求。


结合代码来模拟就是说:

当前这棵权值线段树维护的是1-pos(循环到的r)中,有多少不同的数字。

比如序列 1 2 3 4,那么对于最后一棵权值线段树来说,里面有4个1,代表1-4中有4个不同的数字。如果要找2-4中有几个,那么对最后一棵树进行区间和的查找,找出来是2,对应答案就是2.

比如序列1 2 2 2 3 4 .对于第3棵权值线段树来说,我先在第2棵的基础上减去了这个pos=2的影响(-1),这时候临时权值线段树其叶子节点2的值0.然后我再在第三棵上加上pos=3,这时候第三颗的叶子节点3的权值是1。这时候第三棵权值线段树的1的总和是2,也就是代表1-3中不同的数字总共有2个。如果在第三棵树中找【2,3】有多少不同的数字,那么l>=2的情况下其区间和是1.

比较巧妙

代码:这里


六.求区间mex

P4137 Rmq Problem / mex

黑暗爆炸 - 3585

思路:十分巧妙。首先数值过大,我们将其离散化,对一个序列,其可能出现的答案是a[n+1],0,所以我们在离散化的时候要把a[i]+1放入离散化的b[]数组。[因为有可能答案出现在里面]

对于离散化去重之后的新siz,建立主席树。

对于离散化后的数字,按照出现的次序建立权值线段树。我们维护这个数字在区间内出现的最新位置。然后push_up维护每个节点的min位置。

那么对于第i棵权值线段树,我们在root[i[就是说,在1-i中,每个数字出现的最新位置。

比如1 2 3 4 1 .对于第4棵线段树和第5棵线段树。第4棵最早出现的数字的位置是pos=1,所以root[4]的左儿子的minval是1,如果这时候去[2,4]的话,由于左子树的节点小于l=2,往左子树一直找,最后返回1

然后对于第5棵线段树,1这个数字的叶子权值改成5,push上去root[5]的左儿子的minval不是1了,而是2,这时候去找[2,5],那么就会先去找右儿子,直到找到一个提前离散化处理好的代表没出现的数字5的叶子节点的,其权值为0.对应2  3 4 1 中没有出现的最小数字是0

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=4e5+1000;
typedef long long LL;
LL a[maxn],b[maxn];///离散化
LL cnt=0;
struct Tree{
    LL lson,rson,val;
}tree[maxn<<5];///nlogn
LL root[maxn];
void push_up(LL p){
    tree[p].val=min(tree[tree[p].lson].val,tree[tree[p].rson].val);
}
///k表示输入的数的大小,val表示这次进来的这个数的pos
LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++cnt;///先复制
    tree[rt].val=tree[pre].val;
    tree[rt].lson=tree[pre].lson;
    tree[rt].rson=tree[pre].rson;
    if(l==r) {///叶子节点返回
        tree[rt].val=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid) tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
LL query(LL rt,LL l,LL r,LL x){
    if(l==r) {
        return b[l];
    }
    LL mid=(l+r)>>1;
    if(x<=tree[tree[rt].lson].val){
        return query(tree[rt].rson,mid+1,r,x);
    }
    else return query(tree[rt].lson,l,mid,x);
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  ///freopen("P4137_2.in","r",stdin);
  ///freopen("myoutput2.out","w",stdout);
  LL n,m;cin>>n>>m;
  LL idx=0;
  b[++idx]=0;

  for(LL i=1;i<=n;i++) {
    cin>>a[i];
    b[++idx]=a[i];b[++idx]=a[i]+1;///注意离散化的方式
  }
  sort(b+1,b+1+idx);
  LL siz=unique(b+1,b+1+idx)-b-1;
  for(LL i=1;i<=n;i++){
    LL k=lower_bound(b+1,b+1+siz,a[i])-b;
   /// debug(k);
    root[i]=update(root[i-1],1,siz,k,i);

  }
  while(m--){
    LL l,r;cin>>l>>r;
    LL ans=query(root[r],1,siz,l);
  ///  debug(ans);
    cout<<ans<<endl;
  }
return 0;
}

七.求区间内出现次数大于>=k次的最前数

P3567 [POI2014]KUR-Couriers

CF840D Destiny

他人Fading的讲解:https://www.luogu.com.cn/problem/solution/CF840D


首先你要做过一道题目,\texttt{ P3567 [POI2014]KUR-Couriers} P3567 [POI2014]KUR-Couriers

那道题K=2K=2,但是这道题K\in [2,5]K∈[2,5]。

怎么办呢?考虑那道题的做法,主席树维护[1,x][1,x]内值域区间有多少个数。

然后线段树上二分,首先如果左子树的和\text{sum}_{lson}\leq \frac 12\text{sum}sumlson​≤21​sum,那么\text{sum}_{rson}\geq \frac 12\text{sum}sumrson​≥21​sum。所以那个大往那个子树走即可。

这个题怎么办呢?我们采取暴力,如果\text{su}\text{m}_{lson}\geq \frac 1K\text{su}\text{m}sumlson​≥K1​sum,往左子树走。如果左子树没有答案(即仅仅是和\geq\frac 1K≥K1​),那么往右子树走。

看似时间复杂度不对,实际上是O(nK\log_2 n)O(nKlog2​n)的。

下面是复杂度分析:

一次如果\text{su}\text{m}_{lson}\geq x\frac 1K\text{su}\text{m}(x \in N^*)sumlson​≥xK1​sum(x∈N∗),那么\text{su}\text{m}_{rson}\leq (K-x)\frac{1}{K}\text{su}\text{m}sumrson​≤(K−x)K1​sum。左子树最多就有xx个答案,最多搜索x\log_2nxlog2​n次,同理右边最多搜索(K-x)\log_2n(K−x)log2​n次。


个人理解:其实就是先维护一下区间内数字出现次数有多少次,然后由于比如一个节点的次数和很大,但是并不代表里面的某个叶子节点出现次数>=k,因为可以平分嘛。所以我们就暴力去找。如果左儿子的次数和>=k,那么暴力去找,找到底发现有了,就结束找了,如果找不到,就看右儿子够不够,够就去找,不够就回上一层。

P3567 [POI2014]KUR-Couriers

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=5e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
LL tot=0;
LL a[maxn],root[maxn];
struct Tree{
    LL lson,rson,sum;
}tree[maxn<<5];
void push_up(LL p){
    tree[p].sum=tree[tree[p].lson].sum+tree[tree[p].rson].sum;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++tot;
    ///tree[rt]=tree[pre];
    tree[rt].sum=tree[pre].sum;
    tree[rt].lson=tree[pre].lson;
    tree[rt].rson=tree[pre].rson;
    if(l==r){
        tree[rt].sum+=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    }
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
LL query(LL now,LL pre,LL l,LL r,LL x){
    LL sum=tree[now].sum-tree[pre].sum;
    if(sum<=x) return -1;
    if(l==r) return l;
    LL mid=(l+r)>>1;
    LL ans=-1;
    if(tree[tree[now].lson].sum-tree[tree[pre].lson].sum>x){
        ans=query(tree[now].lson,tree[pre].lson,l,mid,x);
        if(ans>0){
            return ans;
        }
    }
    if(tree[tree[now].rson].sum-tree[tree[pre].rson].sum>x){
        ans=query(tree[now].rson,tree[pre].rson,mid+1,r,x);
        if(ans>0){
            return ans;
        }
    }
    return ans;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n,m;n=read();m=read();
  for(LL i=1;i<=n;i++) a[i]=read();
  for(LL i=1;i<=n;i++){
    root[i]=update(root[i-1],1,n,a[i],1);
  }
  while(m--){
    LL l,r;l=read();r=read();
    LL k=(r-l+1)/2;
    LL ans=query(root[r],root[l-1],1,n,k);
    if(ans==-1){
        puts("0");
    }
    else printf("%lld\n",ans);
  }
return 0;
}

CF840D Destiny

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=3e5+1000;
typedef long long LL;
inline LL read()
{
	LL x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
LL tot=0;
struct Tree{
    LL lson,rson,val;
}tree[maxn<<5];
LL a[maxn],root[maxn];
void push_up(LL p){
    tree[p].val=tree[tree[p].lson].val+tree[tree[p].rson].val;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++tot;
    tree[rt].val=tree[pre].val;
    tree[rt].lson=tree[pre].lson;
    tree[rt].rson=tree[pre].rson;
    if(l==r){///叶子递归边界
        tree[rt].val+=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    }
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
LL query(LL now,LL pre,LL l,LL r,LL x){
    if(tree[now].val-tree[pre].val<=x) return -1;
    if(l==r){
        return l;
    }
    LL res=1e18;
    LL mid=(l+r)>>1;
    if(tree[tree[now].lson].val-tree[tree[pre].lson].val>x){
      LL ans=query(tree[now].lson,tree[pre].lson,l,mid,x);
      if(ans>0){
         res=min(res,ans);
      }
    }
    if(tree[tree[now].rson].val-tree[tree[pre].rson].val>x){
      LL ans=query(tree[now].rson,tree[pre].rson,mid+1,r,x);
      if(ans>0){
         res=min(res,ans);
      }
    }
    return res;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n,m;
  n=read();m=read();
  for(LL i=1;i<=n;i++){
      a[i]=read();
  }
  for(LL i=1;i<=n;i++){
    root[i]=update(root[i-1],1,n,a[i],1);
  }
  while(m--){
     LL l,r,k;l=read();r=read();k=read();
     LL want=(r-l+1)/k;
     LL shu=query(root[r],root[l-1],1,n,want);
     if(shu==1e18) puts("-1");
     else printf("%lld\n",shu);
  }
return 0;
}

八.二分点建主席树

Sign on Fence CodeForces - 484E

P2839 [国家集训队]middle

比较套路:

讲解来自:

Okasaki_Ushio

 

寻找中位数有一个通常的套路:
考虑二分中位数,设x 是现在二分的数,mid是序列的中位数:
将大于x 的数设为1 ,小于x 的数设为−1。
将整个1/−1序列求和
若Sum>0,说明1的数量比−1多,也就是大于x的数比小于x的数多
说明x<=mid
反之x>mid


回到本题,[b+1,c−1]为必选区间,[a,b]选后缀,[c,d]选前缀。
要使中位数尽可能大,我们要使Sum尽量大。
因为[b+1,c−1]固定且[a,b]后[c,d]前缀互不影响,所以在[a,b]区间我们选择最大后缀,在[ c , d ]区间我们选择最大前缀。

至此,我们已经有具体思路了:
线段树维护区间最大前缀,最大后缀,区间和,每次二分,用线段树判断可不可行。


但是,若使用普通线段树,每次查询都要重置区间为1/−1,所以查询时间复杂度为O(nlogn)
不TLE才奇怪。

考虑使用可持久化线段树,把每次二分后的1/−1序列预处理下来,每次查询就是查一个历史版本
总复杂度为O(nlogn+qlog2n)可以AC。


个人理解:对于序列按照权值从小到大排序后,先建立一颗空树。说明右边的数都比他大,此时该线段树的区间和只有一个-1,由此序列建立。

对于答案进行二分是第几个位置,由于我们的线段树是具有区间和从小到大的单调性,所以如果当前树的和>=0.说明还可以往右找,这样子找的数字符合题目要求的满足条件的最大数。找到后把这个编号返回到数组中,输出对应的数字大小。

P2839 [国家集训队]middle

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e4+1000;
typedef long long LL;
inline LL read()
{	LL x=0,f=1;char ch=getchar();	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
LL tot=0;
struct Tree{
    LL lson,rson,pre,suf,sum;///前缀最大,后缀最大,区间和;
}tree[maxn<<6];
LL root[maxn];
struct P{
    LL x,pos;
}a[maxn];
void push_up(LL p){
    tree[p].sum=tree[tree[p].lson].sum+tree[tree[p].rson].sum;
    tree[p].pre=max(tree[tree[p].lson].pre,tree[tree[p].lson].sum+tree[tree[p].rson].pre);
    tree[p].suf=max(tree[tree[p].rson].suf,tree[tree[p].rson].sum+tree[tree[p].lson].suf);
}
LL build(LL l,LL r){
    LL rt=++tot;
    if(l==r){
        tree[rt].pre=tree[rt].suf=tree[rt].sum=1;
        return rt;
    }
    LL mid=(l+r)>>1;
    tree[rt].lson=build(l,mid);
    tree[rt].rson=build(mid+1,r);
    push_up(rt);
    return rt;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++tot;
    tree[rt]=tree[pre];
    if(l==r){
        tree[rt].pre=tree[rt].suf=tree[rt].sum=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    }
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
LL query_sum(LL rt,LL l,LL r,LL L,LL R){
    if(L<=l&&R>=r){
        return tree[rt].sum;
    }
    LL ans=0;
    LL mid=(l+r)>>1;
    ///debug(mid);
    if(L<=mid)  ans+=query_sum(tree[rt].lson,l,mid,L,R);
    if(R>mid) ans+=query_sum(tree[rt].rson,mid+1,r,L,R);
    return ans;
}
LL query_premax(LL rt,LL l,LL r,LL L,LL R){
    if(L<=l&&R>=r){
        return tree[rt].pre;
    }
    LL mid=(l+r)>>1;
   /// debug(mid);
    if(R<=mid){
        return query_premax(tree[rt].lson,l,mid,L,R);
    }
    else if(L>mid){
        return query_premax(tree[rt].rson,mid+1,r,L,R);
    }
    else return max(query_premax(tree[rt].lson,l,mid,L,R),query_sum(tree[rt].lson,l,mid,L,R)+query_premax(tree[rt].rson,mid+1,r,L,R));
}
LL query_sufmax(LL rt,LL l,LL r,LL L,LL R){
    if(L<=l&&R>=r){
        return tree[rt].suf;
    }
    LL mid=(l+r)>>1;
    ///debug(l);debug(r);
    if(R<=mid){
        return query_sufmax(tree[rt].lson,l,mid,L,R);
    }
    else if(L>mid){
        return query_sufmax(tree[rt].rson,mid+1,r,L,R);
    }
    else return max(query_sufmax(tree[rt].rson,mid+1,r,L,R),query_sum(tree[rt].rson,mid+1,r,L,R)+query_sufmax(tree[rt].lson,l,mid,L,R));
}
bool cmp(P A,P B){
    return A.x<B.x;
}
LL n;
LL test[5];
bool check(LL x,LL a,LL b,LL c,LL d){
    LL sum=0;
   /// cout<<"a="<<a<<" "<<"b="<<b<<" "<<"c="<<c<<" "<<"d="<<d<<endl;
    if(! ( (c-1)-(b+1)<=0 ))  sum+=query_sum(root[x],1,n,b+1,c-1);
   /// cout<<"fuck"<<endl;
    sum+=query_sufmax(root[x],1,n,a,b);
    sum+=query_premax(root[x],1,n,c,d);
    if(sum>=0) return true;
    else return false;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  n=read();
  ///题目是从下标0开始的,所以最后的询问算区间的时候要+1
  for(LL i=1;i<=n;i++){
      a[i].x=read();
      a[i].pos=i;
  }
  sort(a+1,a+1+n,cmp);
  root[1]=build(1,n);
  for(LL i=2;i<=n+1;i++){
    root[i]=update(root[i-1],1,n,a[i-1].pos,-1);
  }
  LL Q;Q=read();
  LL last=0;
  while(Q--){
    test[0]=read();test[1]=read();test[2]=read();test[3]=read();
    for(LL i=0;i<4;i++) test[i]=(test[i]+last)%n;
    sort(test,test+4);
    for(LL i=0;i<4;i++){
        test[i]++;
    }
    LL l=1;LL r=n;
    while(l<r){
        LL mid=(l+r+1)>>1;
        if(check(mid,test[0],test[1],test[2],test[3])) l=mid;
        else r=mid-1;
    }
    last=a[l].x;
    printf("%lld\n",a[l].x);

  }
return 0;
}

 

CF484E Sign on Fence

思路:这题很相似。但是如果也按照从小到大去建立,会卡边界。

我是后来模拟发现这个bug的。因为我从小到大建立的时候,是先预处理一颗全1树,然后把当前数字给赋值0,二分l=mid去找。

但是我们要找的区间长度和是要包括这个点的。但是我们已经赋0了。

所以开始处理全0树,把值赋1,二分反为r=mid去找。

query最长连续1的时候注意了,跨越区间的时候要处理仔细。一个比较好的处理方法是在跨区间完了的下一步处理前后缀的最值,通过该区间的最长前缀和要找区间的长度取一个最小然后合并。取完之后再和左右单独区间的最大来获得一个最大。

 

具体看代码容易理解

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}

struct P{
   LL x,pos;
}a[maxn],b[maxn];
struct Tree{
    LL lson,rson,pre,suf,sum;///pre-前缀最长连续1长度,suf后缀最长连续1长度,sum该区间最长连续1的长度
    LL l,r;///该节点代表的区间
}tree[maxn<<5];
LL tot=0;
LL root[maxn];
bool cmp(P A,P B){
    return A.x>B.x;
}
LL rmax(LL A,LL B,LL C){
    return max(A,max(B,C));
}
void push_up(LL rt){
    tree[rt].pre=tree[tree[rt].lson].pre;
    tree[rt].suf=tree[tree[rt].rson].suf;
    tree[rt].sum=rmax(tree[tree[rt].lson].sum,tree[tree[rt].rson].sum,tree[tree[rt].lson].suf+tree[tree[rt].rson].pre);
    if(tree[tree[rt].lson].pre==tree[tree[rt].lson].r-tree[tree[rt].lson].l+1) tree[rt].pre+=tree[tree[rt].rson].pre;
    if(tree[tree[rt].rson].suf==tree[tree[rt].rson].r-tree[tree[rt].rson].l+1) tree[rt].suf+=tree[tree[rt].lson].suf;
}
LL build(LL l,LL r,LL val){
   LL rt=++tot;
   tree[rt].l=l;tree[rt].r=r;
   if(l==r){
    tree[rt].pre=tree[rt].suf=tree[rt].sum=val;
    return rt;
   }
   LL mid=(l+r)>>1;
   tree[rt].lson=build(l,mid,val);
   tree[rt].rson=build(mid+1,r,val);
   push_up(rt);
   return rt;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++tot;
    tree[rt]=tree[pre];
    if(l==r){
        tree[rt].pre=tree[rt].suf=tree[rt].sum=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    }
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
LL query(LL now,LL l,LL r,LL L,LL R){
    if(L<=l&&R>=r) return tree[now].sum;
    LL mid=(l+r)>>1;
    if(R<=mid){
       return query(tree[now].lson,l,mid,L,R);
    }
    else if(L>mid){
        return query(tree[now].rson,mid+1,r,L,R);
    }
    else {
            LL ans1=query(tree[now].lson,l,mid,L,R);
            LL ans2=query(tree[now].rson,mid+1,r,L,R);
            LL res=max(ans1,ans2);
            LL ll=min(tree[tree[now].lson].suf,mid-L+1);///细节
            LL rr=min(tree[tree[now].rson].pre,R-mid);///细节
            res=max(res,ll+rr);
            return res;
    }
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n;n=read();
  for(LL i=1;i<=n;i++){
      a[i].x=read();
      a[i].pos=i;
  }
  sort(a+1,a+1+n,cmp);

  root[0]=build(1,n,0);///开始建全0空树

  for(LL i=1;i<=n;i++){
    root[i]=update(root[i-1],1,n,a[i].pos,1);
  }
  LL Q;Q=read();
  while(Q--){
     LL l,r,k;l=read();r=read();k=read();

     LL L=1;LL R=n;
     while(L<R){
        LL mid=(L+R)>>1;
        LL t=query(root[mid],1,n,l,r);

        if(t>=k)  R=mid;
        else L=mid+1;

     }
     printf("%lld\n",a[L].x);
  }
return 0;
}

Weng_Weijie的讲解:

这题和[国家集训队]middle很像

考虑二分答案

问题变成判断是否存在一段大于等于mid且长度不少于k的子区间

将大于等于mid设为1,小于mid设为0,就是查询区间最长全1区间是否超过k

这个东西可以通过维护前后缀最长全1区间,就支持区间加法了

由于每次二分的mid都不同,于是可以用主席树维护每个mid时的线段树


一点变式题:

The Preliminary Contest for ICPC Asia Xuzhou 2019  I. query

题意:求区间[l,r]内满足min(a[i],a[j])=gcd(a[i],a[j])(l<=i<j<=r)的对数。

思路:实际上是求一个区间内一个数是另外一个数的倍数的有序对数,因为是一个排列,所以容易知道这样的对数不超过nlogn。

nlogn的证明是一个经典的数论trick。序列中是当前数的倍数的数字一定能整除。

那么就有 \sum_{i=1}^{n}(\left \lfloor n\right \rfloorn/i)这玩意算出来是n+n/2+n/3+...n/n=n*(1+1/2+1/3+....1/n) 后面是个调和级数==logn。证明在我的一篇博客里转载过。搜下调和级数的复杂度证明

然后我们就可以预处理每个数字他的因数。

再建立主席树,每颗主席树保存在1-i中是因子对数的个数状态,然后直接在第r颗线段树上查找位置在询问区间里面的个数就好了。

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+100;
typedef int LL;
LL a[maxn],p[maxn];
LL tot=0,root[maxn];
vector<LL>v[maxn];
struct Tree{
    LL lson,rson,val;
}tree[maxn*20*10];
inline void push_up(LL p){
    tree[p].val=tree[tree[p].lson].val+tree[tree[p].rson].val;
}
inline LL update(LL pre,LL l,LL r,LL k,LL val){
    LL rt=++tot;///此题要对root[i]这棵树本身进行多次修改
    tree[rt]=tree[pre];
    if(l==r){
        tree[rt].val+=val;
        return rt;
    }
    LL mid=(l+r)>>1;
    if(k<=mid){
        tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
    }
    else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
    push_up(rt);
    return rt;
}
inline LL query(LL now,LL l,LL r,LL L,LL R){
     if(L<=l&&R>=r){
        return tree[now].val;
     }
     LL ans=0;
     LL mid=(l+r)>>1;
     if(L<=mid) ans+=query(tree[now].lson,l,mid,L,R);
     if(R>mid) ans+=query(tree[now].rson,mid+1,r,L,R);
     return ans;
}
inline LL read(){LL x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
int main(void)
{
  LL n,m;n=read();m=read();
  for(LL i=1;i<=n;i++){
    a[i]=read();
    p[a[i]]=i;
  }
  ///nlogn
  for(LL i=1;i<=n;i++){
    for(LL j=i*2;j<=n;j+=i){
       if(p[i]<p[j]) v[p[j]].push_back(p[i]);
       if(p[i]>p[j]) v[p[i]].push_back(p[j]);
    }
  }
  for(LL i=1;i<=n;i++){
    root[i]=root[i-1];///先复制
    for(auto u:v[i]){
      root[i]=update(root[i],1,n,u,1);
    }
  }
  while(m--){
      LL l,r;l=read();r=read();
      LL ans=query(root[r],1,n,l,r);
      printf("%d\n",ans);
  }
return 0;
}

动态区间第k大(模板)

树状数组套主席树。具体的过程可以看另外一篇博客。学完之后有了一点新的理解。

P2617 Dynamic Rankings(动态区间第k大——树状数组套主席树)详解

特别理解到的一点:

update的过程,在静态主席树查询的过程中,代码是这样的:每次可以新开一颗主席树,不过更新的是和前一棵有区别的logn长链,剩下的直接指回去就好了。

但是注意不是所有情况都是新开点的。这个题的情况就是在原有的节点上覆盖更新。

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=1e5+100;
typedef long long LL;
struct Tree{
    LL l,r,rt,sum;
}tree[maxn*4*35];
struct operation{bool b;LL l,r,k;LL pos,t;}q[maxn];
LL n,m,a[maxn],root[maxn],o[maxn*2],tot=0,len=0;
LL temp[2][30],cnt[10];
void modify(LL &now,LL l,LL r,LL x,LL val){
    if(!now) now=++tot;
    tree[now].sum+=val;
    if(l==r) return;
    LL mid=(l+r)>>1;
    if(x<=mid) modify(tree[now].l,l,mid,x,val);
    else modify(tree[now].r,mid+1,r,x,val);
}
void pre_modify(LL pos,LL val){
    LL x=lower_bound(o+1,o+1+len,a[pos])-o;
    for(LL i=pos;i<=n;i+=lowbit(i)){
        modify(root[i],1,len,x,val);///处理出需要修改哪log棵主席树
    }
}
LL query(LL l,LL r,LL k){
    if(l==r) return l;
    LL mid=(l+r)>>1;LL sum=0;
    for(LL i=1;i<=cnt[1];i++) sum+=tree[tree[temp[1][i]].l].sum;
    for(LL i=1;i<=cnt[0];i++) sum-=tree[tree[temp[0][i]].l].sum;
    if(k<=sum){
        for(LL i=1;i<=cnt[1];i++){
            temp[1][i]=tree[temp[1][i]].l;
        }
        for(LL i=1;i<=cnt[0];i++){
            temp[0][i]=tree[temp[0][i]].l;
        }
        return query(l,mid,k);
    }
    else{
        for(LL i=1;i<=cnt[1];i++){
            temp[1][i]=tree[temp[1][i]].r;
        }
        for(LL i=1;i<=cnt[0];i++){
            temp[0][i]=tree[temp[0][i]].r;
        }
        return query(mid+1,r,k-sum);
    }
}
LL pre_query(LL l,LL r,LL k){
    memset(temp,0,sizeof(temp));
    cnt[0]=cnt[1]=0;
    for(LL i=r;i;i-=lowbit(i)) temp[1][++cnt[1]]=root[i];
    for(LL i=l-1;i;i-=lowbit(i)) temp[0][++cnt[0]]=root[i];
    return query(1,len,k);
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  cin>>n>>m;
  for(LL i=1;i<=n;i++){
    cin>>a[i];
    o[++len]=a[i];
  }
  for(LL i=1;i<=m;i++){
    char op;cin>>op;
    if(op=='Q'){
        q[i].b=1; cin>>q[i].l>>q[i].r>>q[i].k;
    }
    else{
        q[i].b=0;
        cin>>q[i].pos>>q[i].t;
        o[++len]=q[i].t;
    }
  }
  sort(o+1,o+len+1);
  len=unique(o+1,o+len+1)-o-1;
  for(LL i=1;i<=n;i++){
    pre_modify(i,1);
  }
  for(LL i=1;i<=m;i++){
    if(q[i].b==1){
        cout<<o[pre_query(q[i].l,q[i].r,q[i].k)]<<endl;
    }
    else{
        pre_modify(q[i].pos,-1);
        a[q[i].pos]=q[i].t;
        pre_modify(q[i].pos,1);
    }
  }
return 0;
}

小结一下:主席树的维护还是相当需要思维含量的。不过好像大体建立好后要不在root[r]中找,要不通过root[r]-root[l]中找。剩下的就是看积累和思维了把,还有一定的套路固定的题型。

(希望期末月人没事我好菜啊

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值