zoukankan      html  css  js  c++  java
  • 【浮*光】#数据结构# 数据结构の相关练习题

    那些年,我们做过的数据结构题...

    T1:【p3792】由乃与大母神原型

    • 1.单点修改;2.查询区间l、r是否可以重排为值域上连续的一段。

    线段树维护区间min、区间max、区间和、区间平方和。

    通过min和max算出,如果是连续段、‘和’和‘平方和’应该是多少。

    类似hash的思想。但平方和可能被卡,可以用立方和处理。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p3792】由乃...(lxl果然毒瘤...)
    1.单点修改;2.查询区间l、r是否可以重排为值域上连续的一段。*/
    
    //线段树维护区间min、区间max、区间和、区间平方和。
    //通过min和max算出,如果是连续段、‘和’和‘平方和’应该是多少。
    //类似hash的思想。但平方和可能被卡,可以用立方和处理。
    
    void reads(ll &x){ //读入优化(正负整数)
        ll f=1;x=0;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        x*=f; //正负号
    }
    
    struct node{ double max; ll ans; }seg[500019];
    
    const ll N=500019,mod=1000000007;
    
    ll n,m,op,x,y,a[N];
    
    inline ll cube(ll x){return x*x%mod*x%mod;} //立方
    
    inline ll sqr(ll x){return x*x%mod;} //平方
    
    ll Min[N<<2],Sum[N<<2]; //区间最小值,区间立方和
    
    inline void push_up(ll rt)
     { Min[rt]=min(Min[rt<<1],Min[rt<<1|1]),Sum[rt]=(Sum[rt<<1]+Sum[rt<<1|1])%mod; }
    
    inline void build(ll rt,ll l,ll r){
        if(l==r){Min[rt]=a[l],Sum[rt]=cube(a[l]);return;}
        ll mid=(l+r)>>1; build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); push_up(rt); }
    
    inline void update(ll rt,ll l,ll r,ll p,ll x){
        if(l==r){Min[rt]=x,Sum[rt]=cube(x);return;}
        ll mid=(l+r)>>1; if(p<=mid) update(rt<<1,l,mid,p,x);
        else update(rt<<1|1,mid+1,r,p,x); push_up(rt); }
    
    inline ll Query_Min(ll rt,ll l,ll r,ll ql,ll qr){
        if(l>=ql&&r<=qr) return Min[rt]; ll mid=(l+r)>>1;
        if(qr<=mid) return Query_Min(rt<<1,l,mid,ql,qr);
        else if(ql>mid) return Query_Min(rt<<1|1,mid+1,r,ql,qr);
        else return min(Query_Min(rt<<1,l,mid,ql,qr),Query_Min(rt<<1|1,mid+1,r,ql,qr)); }
    
    inline ll Query_Sum(ll rt,ll l,ll r,ll ql,ll qr){
        if (l>=ql&&r<=qr) return Sum[rt]; ll mid=(l+r)>>1;
        if (qr<=mid) return Query_Sum(rt<<1,l,mid,ql,qr);
        else if (ql>mid) return Query_Sum(rt<<1|1,mid+1,r,ql,qr);
        else return (Query_Sum(rt<<1,l,mid,ql,qr)+Query_Sum(rt<<1|1,mid+1,r,ql,qr))%mod;
    }
    
    ll sum2[N],sum3[N]; //前缀平方、立方和,便于比较
    
    int main(){
        reads(n),reads(m); for(ll i=1;i<=n;i++) reads(a[i]);
        build(1,1,n); //↓↓预处理区间前缀平方、立方和,便于比较
        for(int i=1;i<=n;i++) sum2[i]=(sum2[i-1]+sqr(i))%mod,
            sum3[i]=(sum3[i-1]+cube(i))%mod;
        while(m--){ reads(op),reads(x),reads(y);
            if(op==1){ update(1,1,n,x,y); continue; }
            ll Minn=Query_Min(1,1,n,x,y),Summ=Query_Sum(1,1,n,x,y);
            ll Sums=((y-x+1)*cube(Minn)%mod+sum3[y-x] //连续段的立方和
                +(y-x+1)*(y-x)/2%mod*3*Minn%mod*Minn%mod+sum2[y-x]*3%mod*Minn%mod)%mod;
            (Sums==Summ)?puts("damushen"):puts("yuanxing");
        }
    }
    【p3792】由乃与大母神原型 // 用hash判断:子段是否相同

    T2:【uva1400】 "Ray, Pass me the dishes!"

    • 一个长度为n的整数序列D,对m个询问做出回答。
    • 对于询问(a,b),需要找到两个下标x和y,
    • 使得a<=x<=y<=b,并且Dx+Dx+1+....+Dy尽量大。
    • 如果有多组满足条件的x和y,x、y尽量小。

    前缀max rt.pre=max(ls.pre,ls.sum+rs.pre);

    后缀max rt.suf=max(rs.suf,rs.sum+ls.suf);

    子段和 rt.sub=max(max(ls.sub,rs.sub),ls.suf+rs.pre);

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<set>
    using namespace std;
    typedef long long LL; //防止重名
    
    /*【uva1400】Ray???
    一个长度为n的整数序列D,对m个询问做出回答。
    对于询问(rt,b),需要找到两个下标x和y,
    使得a<=x<=y<=b,并且Dx+Dx+1+....+Dy尽量大。
    如果有多组满足条件的x和y,x、y尽量小。*/
    
    //前缀max rt.pre=max(ls.pre,ls.sum+rs.pre);
    //后缀max rt.suf=max(rs.suf,rs.sum+ls.suf);
    //子段和 rt.sub=max(max(ls.sub,rs.sub),ls.suf+rs.pre);
    
    const LL N=5e5+19; LL n,m,d[N],kase;
    
    struct Tree{ LL pre,suf,sub,sum,lch,rch,ll,rr; }tree[N<<2];
    //最后面的四个值表示:子段和左端点,右端点,前缀和右端点,后缀和左端点
    
    void init(){ memset(d,0,sizeof(d)),memset(tree,0,sizeof(tree)); }
    
    void push_up(Tree &rt,Tree ls,Tree rs){ //要分情况讨论,记录更新llrr等信息
        
        rt.pre=rt.suf=rt.sub=rt.sum=0; rt.sum=ls.sum+rs.sum;
        
        if((ls.sum+rs.pre)<=ls.pre) rt.pre=ls.pre,rt.ll=ls.ll;
        else rt.pre=ls.sum+rs.pre,rt.ll=rs.ll;
        
        if((rs.sum+ls.suf)<=rs.suf) rt.suf=rs.suf,rt.rr=rs.rr;
        else rt.suf=rs.sum+ls.suf,rt.rr=ls.rr;
    
        if((ls.sub>=rs.sub)&&(ls.sub>=ls.suf+rs.pre))
            rt.sub=ls.sub,rt.lch=ls.lch,rt.rch=ls.rch;
    
        else if((ls.suf+rs.pre>=ls.sub)&&(ls.suf+rs.pre>=rs.sub))
            rt.sub=ls.suf+rs.pre,rt.lch=ls.rr,rt.rch=rs.ll;
    
        else rt.sub=rs.sub,rt.lch=rs.lch,rt.rch=rs.rch;
    }
    
    void build(LL rt,LL l,LL r){
        if(l==r){ tree[rt].pre=tree[rt].suf=tree[rt].sub=tree[rt].sum=d[l];
          tree[rt].ll=tree[rt].rr=tree[rt].lch=tree[rt].rch=l; return; } 
        LL mid=(l+r)>>1; build(rt<<1,l,mid),build(rt<<1|1,mid+1,r);
        push_up(tree[rt],tree[rt<<1],tree[rt<<1|1]);
    }
    
    Tree query(LL rt,LL l,LL r,LL x,LL y){
        Tree ls,rs,w; LL pd1=0,pd2=0,mid;
        if(l>=x&&r<=y) return tree[rt]; mid=(l+r)>>1;
        if(x<=mid) ls=query(rt<<1,l,mid,x,y),pd1=1;
        if(y>mid) rs=query(rt<<1|1,mid+1,r,x,y),pd2=1;
        if(pd1&&pd2) push_up(w,ls,rs); //统计区间
        else if(pd1) w=ls; else if(pd2) w=rs; return w;
    }
    
    int main(){ freopen("test.in","r",stdin);
        freopen("test.out","w",stdout);
        while(~scanf("%lld%lld",&n,&m)){
            init(),printf("Case %lld:
    ",++kase);
            for(LL i=1;i<=n;i++) scanf("%lld",&d[i]);
            build(1,1,n); //建树,并push_up
            for(LL i=1,a,b;i<=m;i++){ Tree c;
                scanf("%lld%lld",&a,&b),c=query(1,1,n,a,b);
                printf("%lld %lld
    ",c.lch,c.rch);
            }
        }
    }
    【uva1400】Ray // 复杂的线段树区间合并 // 一言难尽的wa...

    T3:【p4198】楼房重建

    • 每天改变某楼的高度,问每天能看见的楼数。

    线段树维护斜率(递增序列)。即:维护区间max高度 和 区间答案(可以看见的个数)。

    如何合并子区间?已知区间第一项和区间max,根据左区间的情况,确定右区间的贡献。

    即:Seg[rt].ans=左儿子的ans+计算一下右儿子能做出的贡献。

    如何计算右儿子贡献?递归,与左边maxx值比较,分类统计。

    注意一些细节中,利用了上升序列的性质,减少了很多的合并时间。

    这个题的细节处理很好,有很多巧妙的操作~ 差不多就是,注意左右区间最值合并时的性质。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p4198】楼房重建 
    每天改变某楼的高度,问每天能看见的楼数。*/
    
    //线段树维护斜率(递增)。即:维护区间max高度,区间答案(可以看见的个数)。
    //如何合并子区间?已知区间第一项和区间max,根据左区间的情况,确定右区间的贡献。
    //即:Seg[rt].ans=左儿子的ans+计算一下右儿子能做出的贡献。
    
    //如何计算右儿子贡献?递归,与左边maxx值比较,分类统计。
    
    void reads(int &x){ //读入优化(正负整数)
        int f=1;x=0;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        x*=f; //正负号
    }
    
    struct node{ double max; int ans; }seg[500019];
    
    int calc_(int rt,double maxx,int l,int r){ //↓↓递归到叶子
        int mid=(l+r)>>1; if(l==r) return seg[rt].max>maxx?1:0;
        if(seg[rt].max<=maxx) return 0; //无答案的区间
        if(seg[rt<<1].max<=maxx) return calc_(rt<<1|1,maxx,mid+1,r); 
        //↑↑只有此节点(原来的右儿子节点)中的右子树中有答案 
        else return calc_(rt<<1,maxx,l,mid)+seg[rt].ans-seg[rt<<1].ans;
        //↑↑左边的答案+右边能大于左边的答案(整体区间可行-左边区间可行)
    }
    
    void update(int rt,int l,int r,int x,double k){ //位置、斜率
        if(l==r&&l==x){ seg[rt].max=k,seg[rt].ans=1; return; }
        int mid=(l+r)>>1; if(x<=mid) update(rt<<1,l,mid,x,k);
        else update(rt<<1|1,mid+1,r,x,k); //递归左右儿子
        seg[rt].max=max(seg[rt<<1].max,seg[rt<<1|1].max);
        seg[rt].ans=seg[rt<<1].ans+calc_(rt<<1|1,seg[rt<<1].max,mid+1,r);
    }
    
    int main(){ 
        int n,m,x,y; reads(n),reads(m);
        for(int i=1;i<=m;i++){ //↓↓修改位置 和 修改值(斜率)
            reads(x),reads(y),update(1,1,n,x,y*1.0/x);
            printf("%d
    ",seg[1].ans); //总答案
        }
    }
    【p4198】楼房重建 // 线段树区间合并 + 细节处理【巧妙】

    T4:【p5094】狂欢节

    • 求n*(n-1)/2对奶牛的max⁡(vi,vj)×dis(i,j)之和。

     树状数组维护:

            int num=query_num(a[i].pos); //坐标小于xi的奶牛的数量

            int sum=query_sum(a[i].pos); //坐标小于xi的奶牛的坐标之和

    同时记录一个all,表示前面所有坐标之和。因为已知i,所以可以统计为:

            在i前面所有坐标小于xi的奶牛: s1=xi∗num−sum。

            在i前面所有坐标大于xi的奶牛: s2=(all−sum)−(i−1−num)∗xi。​

    #include <bits/stdc++.h>
    #define int long long
    using namespace std;
    
    //【p5094】狂欢节 //树状数组
    
    // https://www.luogu.org/blog/top-oier/solution-p5094
    
    const int MAXN=20019;
    
    struct cow{ int voice,pos; }a[MAXN]; int n;
    
    bool cmp(cow x,cow y){ return x.voice<y.voice; }
    
    int c1[MAXN],c2[MAXN];
    
    inline int lowbit(int x){ return x&(-x); }
    
    int query_num(int p)
     { int res=0; for(;p;p-=lowbit(p)) res+=c1[p]; return res; }
    
    int query_sum(int p)
     { int res=0; for(;p;p-=lowbit(p)) res+=c2[p]; return res; }
    
    void add_num(int p,int x){ for(;p<MAXN;p+=lowbit(p)) c1[p]+=x; }
    
    void add_sum(int p,int x){ for(;p<MAXN;p+=lowbit(p)) c2[p]+=x; }
    
    int main(){
        cin>>n; for(int i=1;i<=n;i++) cin>>a[i].voice>>a[i].pos;
        sort(a+1,a+n+1,cmp); int ans=0,all=0;
        for(int i=1;i<=n;i++){ //树状数组维护:
            int num=query_num(a[i].pos); //坐标小于xi的奶牛的数量
            int sum=query_sum(a[i].pos); //坐标小于xi的奶牛的坐标之和
            ans+=(num*a[i].pos-sum)*a[i].voice;
            ans+=((all-sum)-(i-1-num)*a[i].pos)*a[i].voice;
            add_num(a[i].pos,1),add_sum(a[i].pos,a[i].pos);
            all+=a[i].pos; //all表示x前所有牛的坐标之和(大于,小于xi)
        } cout<<ans<<endl; return 0; //O(nlogn)
    }
    【p5094】狂欢节 // 树状数组

     T5:【uva11992】快速矩阵操作

    有一个r行c列的全0矩阵,有以下三种操作。

    •   1 X1 Y1 X2 Y2 v 子矩阵(X1,Y1,X2,Y2)的元素加v。
    •   2 X1 Y1 X2 Y2 v 子矩阵(X1,Y1,X2,Y2)的元素变为v。
    •   3 X1 Y1 X2 Y2 查询子矩阵(X1,Y1,X2,Y2)的和,最大值,最小值。

    子矩阵(X1,Y1,X2,Y2)即满足X1<=X<=X2 Y1<=Y<=Y2的所有元素(X1,Y2)。

    输入保证和不超过10^9。数据范围:r <= 20。

    【分析】由数据范围可知,可以建立r棵线段树,在每棵上面维护区间。

           注意:有set_(替换)和add_(增加)两个标记,但set_优先于add_。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<set>
    using namespace std;
    typedef long long LL; //防止重名
    
    /*【uva11992】快速矩阵操作
    有一个r行c列的全0矩阵,有以下三种操作。
      1 X1 Y1 X2 Y2 v 子矩阵(X1,Y1,X2,Y2)的元素加v。
      2 X1 Y1 X2 Y2 v 子矩阵(X1,Y1,X2,Y2)的元素变为v。
      3 X1 Y1 X2 Y2 查询子矩阵(X1,Y1,X2,Y2)的和,最大值,最小值。
    子矩阵(X1,Y1,X2,Y2)即满足X1<=X<=X2 Y1<=Y<=Y2的所有元素(X1,Y2)。
    输入保证和不超过10^9。数据范围:r <= 20。*/
    
    //【分析】由数据范围可知,可以建立r棵线段树,在每棵上面维护区间。
    //       注意,有set_和add_两个标记,set_优先于add_。
    
    void reads(int &x){ //读入优化(正负整数)
        int fx_=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fx_; //正负号
    }
    
    const int N=10000019; int r,c,m,tot=0,tree[22];
    
    struct Tree{ int maxx,minn,sum,ls,rs,add,set; }seg[N];
    
    void push_up(int rt){ seg[rt].sum=seg[seg[rt].ls].sum+seg[seg[rt].rs].sum;
      seg[rt].minn=min(seg[seg[rt].ls].minn,seg[seg[rt].rs].minn);
      seg[rt].maxx=max(seg[seg[rt].ls].maxx,seg[seg[rt].rs].maxx); }
    
    void push_down(int rt,int l,int r){
        
        int mid=(l+r)>>1;
        
        if(seg[rt].set!=0){ //先放set标记
            seg[seg[rt].ls].set=seg[seg[rt].rs].set=seg[rt].set;
            seg[seg[rt].ls].sum=seg[rt].set*(mid-l+1);
            seg[seg[rt].rs].sum=seg[rt].set*(r-mid);
            seg[seg[rt].ls].maxx=seg[seg[rt].rs].maxx=seg[rt].set;
            seg[seg[rt].ls].minn=seg[seg[rt].rs].minn=seg[rt].set;
            seg[seg[rt].ls].add=seg[seg[rt].rs].add=seg[rt].set=0; 
        } 
        
        if(seg[rt].add!=0){ //再放add标记
            seg[seg[rt].ls].sum+=seg[rt].add*(mid-l+1);
            seg[seg[rt].rs].sum+=seg[rt].add*(r-mid);
            seg[seg[rt].ls].maxx+=seg[rt].add,seg[seg[rt].rs].maxx+=seg[rt].add;
            seg[seg[rt].ls].minn+=seg[rt].add,seg[seg[rt].rs].minn+=seg[rt].add;
            seg[seg[rt].ls].add+=seg[rt].add, //注意:加法标记不能直接继承,要+
            seg[seg[rt].rs].add+=seg[rt].add,seg[rt].add=0;
        }
    }
    
    int build(int l,int r){
        int rt=++tot; seg[rt].add=seg[rt].set=0; 
        seg[rt].sum=seg[rt].maxx=seg[rt].minn=0; //多组数据
        if(l==r){ seg[rt].sum=seg[rt].maxx=seg[rt].minn=0;
            seg[rt].ls=seg[rt].rs=0; return rt; }
        int mid=(l+r)>>1; seg[rt].ls=build(l,mid); //动态开点
        seg[rt].rs=build(mid+1,r); push_up(rt); return rt;
    }
    
    void _add(int rt,int l,int r,int ql,int qr,int v){
        if(ql<=l&&qr>=r){ seg[rt].add+=v,seg[rt].sum+=v*(r-l+1),
            seg[rt].maxx+=v,seg[rt].minn+=v; return;
        } push_down(rt,l,r); int mid=(l+r)>>1; 
        if(ql<=mid) _add(seg[rt].ls,l,mid,ql,qr,v);
        if(mid<qr) _add(seg[rt].rs,mid+1,r,ql,qr,v); push_up(rt);
    }
    
    void _set(int rt,int l,int r,int ql,int qr,int v){
        if(ql<=l&&qr>=r){ seg[rt].add=0,seg[rt].set=v, //add标记要清零
            seg[rt].sum=v*(r-l+1),seg[rt].maxx=v,seg[rt].minn=v; return;
        } push_down(rt,l,r); int mid=(l+r)>>1; 
        if(ql<=mid) _set(seg[rt].ls,l,mid,ql,qr,v);
        if(mid<qr) _set(seg[rt].rs,mid+1,r,ql,qr,v); push_up(rt);
    }
    
    int sum_query(int rt,int l,int r,int ql,int qr){
      if(ql<=l&&qr>=r) return seg[rt].sum; push_down(rt,l,r); 
      int mid=(l+r)>>1,ans=0; //注意ans初始值的设定
      if(ql<=mid) ans+=sum_query(seg[rt].ls,l,mid,ql,qr);
      if(qr>mid) ans+=sum_query(seg[rt].rs,mid+1,r,ql,qr); return ans; }
    
    int max_query(int rt,int l,int r,int ql,int qr){
      if(ql<=l&&qr>=r) return seg[rt].maxx; push_down(rt,l,r); 
      int mid=(l+r)>>1,ans=-2e9; //注意ans初始值的设定
      if(ql<=mid) ans=max(ans,max_query(seg[rt].ls,l,mid,ql,qr));
      if(qr>mid) ans=max(ans,max_query(seg[rt].rs,mid+1,r,ql,qr)); return ans; }
    
    int min_query(int rt,int l,int r,int ql,int qr){
      if(ql<=l&&qr>=r) return seg[rt].minn; push_down(rt,l,r);
      int mid=(l+r)>>1,ans=2e9; //注意ans初始值的设定
      if(ql<=mid) ans=min(ans,min_query(seg[rt].ls,l,mid,ql,qr));
      if(qr>mid) ans=min(ans,min_query(seg[rt].rs,mid+1,r,ql,qr)); return ans; }
    
    int main(){
        while(~scanf("%d%d%d",&r,&c,&m)){ //↓↓每行一棵线段树,tree记录rt编号
          tot=0; for(int i=1;i<=r;i++) tree[i]=build(1,c);
          for(int i=1,op,x1,x2,yi,y2,v;i<=m;i++){
              reads(op),reads(x1),reads(yi),reads(x2),reads(y2);
              if(op==1){ reads(v); //在 维护x1~x2行的线段树上 修改
                for(int i=x1;i<=x2;i++) _add(tree[i],1,c,yi,y2,v); }
              if(op==2){ reads(v); //在 维护x1~x2行的线段树上 赋值
                for(int i=x1;i<=x2;i++) _set(tree[i],1,c,yi,y2,v); }
              if(op==3){ int sum_=0,min_=(int)2e9,max_=(int)-2e9;
                for(int i=x1;i<=x2;i++){
                  sum_+=sum_query(tree[i],1,c,yi,y2);
                  min_=min(min_,min_query(tree[i],1,c,yi,y2));
                  max_=max(max_,max_query(tree[i],1,c,yi,y2));
              } printf("%d %d %d
    ",sum_,min_,max_); }
          }
        }
    }
    【uva11992】快速矩阵操作 // 多棵线段树

     T6:【CF833B】The Bakery

    • 将一个长度为n的序列分为k段,使得总价值最大。
    • 一段区间的价值表示为区间内不同数字的个数。

    【分析】dp[j][i]=max{dp[j-1][k]+cnt[k+1][i]}

    dp[i][j]意为1~i之间分割j次所产生的最大值;cnt[i][j]表示i-j之间不同的颜色个数。

    看到max可以考虑线段树优化,即:需要在O(logn)的时间内计算出cnt[i][j]。

    可知:一种颜色能够产生贡献的范围 = 他上一次出现的位置 ~ 他当前的位置。

    此颜色如果在范围内,产生1贡献,所以可以使用线段树区间加,给pre[i]~i都加上1。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<set>
    using namespace std;
    typedef long long ll;
    
    /*【CF833B】The Bakery
    将一个长度为n的序列分为k段,使得总价值最大。
    一段区间的价值表示为区间内不同数字的个数。 */
    
    /*【分析】dp[j][i]=max{dp[j-1][k]+cnt[k+1][i]}
    dp[i][j]意为1~i之间分割j次所产生的最大值;cnt[i][j]表示i-j之间不同的颜色个数。
    看到max可以考虑线段树优化,即:需要在O(logn)的时间内计算出cnt[i][j]。
    可知:一种颜色能够产生贡献的范围 = 他上一次出现的位置 ~ 他当前的位置。
    此颜色如果在范围内,产生1贡献,所以可以使用线段树区间加,给pre[i]~i都加上1。*/
    
    void reads(int &x){ //读入优化(正负整数)
        int fx_=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fx_; //正负号
    }
    
    #define ls rt<<1
    
    #define rs rt<<1|1 
    
    struct node{ int l,r,sum,tag; }seg[1500019];
    
    int dp[59][40000],pre[40000],pos[40000];
    
    void init(){ memset(seg,0,sizeof(seg)); }
    
    void push_up(int rt)
     { seg[rt].sum=max(seg[ls].sum,seg[rs].sum); }
    
    void push_down(int rt){ seg[ls].sum+=seg[rt].tag,seg[ls].tag+=seg[rt].tag;
      seg[rs].sum+=seg[rt].tag,seg[rs].tag+=seg[rt].tag,seg[rt].tag=0; }
    
    void build(int rt,int l,int r,int now){
      if(l==r){ seg[rt].l=l,seg[rt].r=r;
        seg[rt].sum=dp[now][l-1]; return; }
      seg[rt].l=l,seg[rt].r=r; int mid=(l+r)>>1;
      build(ls,l,mid,now),build(rs,mid+1,r,now),push_up(rt); }
    
    void update(int rt,int l,int r,int val){
      if(seg[rt].l==l&&seg[rt].r==r)
       { seg[rt].sum+=val,seg[rt].tag+=val; return; }
      if(seg[rt].tag) push_down(rt); int mid=(seg[rt].l+seg[rt].r)>>1;
      if(mid<l) update(rs,l,r,val); else if(mid>=r) update(ls,l,r,val);
      else update(ls,l,mid,val),update(rs,mid+1,r,val); push_up(rt); }
    
    int query(int rt,int l,int r){
      if(seg[rt].l==l&&seg[rt].r==r) return seg[rt].sum;
      if(seg[rt].tag) push_down(rt);
      int mid=(seg[rt].l+seg[rt].r)>>1;
      if(mid<l) return query(rs,l,r);
      else if(mid>=r) return query(ls,l,r);
      else return max(query(ls,l,mid),query(rs,mid+1,r)); }
    
    int main(){
        int n,k,t; reads(n),reads(k); //↓↓记录上一次出现的位置
        for(int i=1;i<=n;i++) reads(t),pre[i]=pos[t]+1,pos[t]=i;
        for(int i=1;i<=k;i++){ //分成k段
            init(); build(1,1,n,i-1); //每次都要根据上一段dp值重新建树
            for(int j=1;j<=n;j++) //dp[i][now]=max{dp[i-1][las]+cnt[now][las+1]}
                update(1,pre[j],j,1),dp[i][j]=query(1,1,j);
        } printf("%d
    ",dp[k][n]); return 0;
    }
    【CF833B】The Bakery //线段树优化dp

    T7:【p3939】数颜色

    • 1 lj rj cj :询问在区间[lj​,rj​]里有多少只颜色为cj​的兔子;
    • 2 xj: xj 和 xj+1 两只兔子交换了位置。

    主席树维护区间历史版本的信息,对于操作2:

    update(rt[k],rt[k],1,MAX,col[k],-1),update(rt[k],rt[k],1,MAX,col[k+1],1),

    update(rt[k+1],rt[k+1],1,MAX,col[k+1],-1),update(rt[k+1],rt[k+1],1,MAX,col[k],1),

    ↑↑ 注意,k+1处统计的是1~k+1的信息,k与k+1替换了,并不改变k+1的前缀信息

    然后再 swap(col[k],col[k+1]); 即:在主席树的历史版本k处改变col[k],col[k+1]的个数。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p3939】数颜色
    1 lj rj cj :询问在区间[lj​,rj​]里有多少只颜色为cj​的兔子;
    2 xj: xj 和 xj+1 两只兔子交换了位置。*/
    
    void reads(int &x_){ //读入优化(正负整数)
        int fx_=1;x_=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();}
        while(s>='0'&&s<='9'){x_=(x_<<3)+(x_<<1)+s-'0';s=getchar();}
        x_*=fx_; //正负号
    }
    
    void write(int x){ if(x<0) putchar('-'),x=-x;
      if(x>9) write(x/10); putchar(x%10+'0'); }
    
    const int N=20000019,MAX=300000;
    
    int n,m,node_cnt=0,col[500019],sum[N],rt[N],ls[N],rs[N];
    
    void update(int las_,int &now_,int l,int r,int val,int op){
        now_=++node_cnt; //主席树动态开点:寻找新值val的位置,建出这条新链
        ls[now_]=ls[las_],rs[now_]=rs[las_],sum[now_]=sum[las_]+op;
        if(l==r) return; int mid=(l+r)>>1;
        if(val<=mid) update(ls[las_],ls[now_],l,mid,val,op);
        else update(rs[las_],rs[now_],mid+1,r,val,op);
    }
    
    int query(int u,int v,int l,int r,int k){ //查询区间中颜色k的个数
        if(l==r) return sum[v]-sum[u]; int mid=(l+r)>>1;
        if(k<=mid) return query(ls[u],ls[v],l,mid,k);
        else return query(rs[u],rs[v],mid+1,r,k); //递归子树寻找位置k
    }
    
    int main(){
        reads(n),reads(m); for(int i=1;i<=n;i++) 
          reads(col[i]),update(rt[i-1],rt[i],1,MAX,col[i],1);
        for(int i=1,op,l,r,k;i<=m;i++){ reads(op);
            if(op==1) reads(l),reads(r),reads(k), //寻找区间颜色k的个数
                write(query(rt[l-1],rt[r],1,MAX,k)),puts("");
            if(op==2) reads(k),update(rt[k],rt[k],1,MAX,col[k],-1),
                update(rt[k],rt[k],1,MAX,col[k+1],1),
                //update(rt[k+1],rt[k+1],1,MAX,col[k+1],-1),
                //update(rt[k+1],rt[k+1],1,MAX,col[k],1),
                //↑↑注意,k+1处统计的是1~k+1的信息,k与k+1替换了,并不改变k+1的前缀信息
                swap(col[k],col[k+1]); //在主席树的历史版本k处改变col[k/k+1]的个数
        }
    }
    【p3939】数颜色 //主席树

     T8:【sp3946】kth num

    • 查询区间kth(主席树模板题)。
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    //【sp3946】kth num
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fx; //正负号
    }
    
    const int N=200019;
    
    int node_cnt,n,m,sum[N*30],rt[N],lc[N*30],rc[N*30];
    
    int a[N],b[N],p; //原序列和离散序列,p:此点在总体中的排名
    
    void build(int &rt,int l,int r){ rt=++node_cnt; if(l==r) return;
      int mid=(l+r)>>1; build(lc[rt],l,mid),build(rc[rt],mid+1,r); }
    
    // ↑↑ 注意动态开点的建树方法,递归建立节点 lc[rt]、rc[rt]。
    
    int modify(int _rt,int l,int r){ //在_rt的基础上,新建一条链now_
        int now_=++node_cnt; //每次都要开新节点,复制已经记录的下层的位置
        lc[now_]=lc[_rt],rc[now_]=rc[_rt],sum[now_]=sum[_rt]+1;
        if(l==r) return now_; //已经递归到新树(链)的根
        int mid=(l+r)>>1; //↓↓按左右子树不同,分类复制上次的位置
        if(p<=mid) lc[now_]=modify(lc[now_],l,mid);
        else rc[now_]=modify(rc[now_],mid+1,r); return now_; 
        //递归,处理得出此链上每个节点的左右儿子↑↑ 最终在主程序中返回rt[i]的编号
    }
    
    int query(int u,int v,int l,int r,int k){ //查询区间Kth
        int ans_id,mid=((l+r)>>1),x=sum[lc[v]]-sum[lc[u]]; //x:左边的个数
        if(l==r) return l; //走到叶子节点,此时的编号(在排序后的数组中)就代表区间Kth
        if(x>=k) ans_id=query(lc[u],lc[v],l,mid,k); //左边个数足够,递归左子树
        else ans_id=query(rc[u],rc[v],mid+1,r,k-x); return ans_id;
    }
    
    int main(){
        int bn_,ans_id; reads(n),reads(m);
        for(int i=1;i<=n;i++) reads(a[i]),b[i]=a[i];
        sort(b+1, b+n+1); bn_=unique(b+1,b+n+1)-b-1;
        build(rt[0],1,bn_); //初始空树(便于线段树合并)
        for(int i=1;i<=n;i++){
            p=lower_bound(b+1,b+bn_+1,a[i])-b; //二分查找a[i]在b中的排名
            rt[i]=modify(rt[i-1],1,bn_); //在前一棵树的基础上,递归区间(1,bn_),加入一条链
        } while(m--){ int l,r,k; reads(l),reads(r),reads(k);
            ans_id=query(rt[l-1],rt[r],1,bn_,k); printf("%d
    ",b[ans_id]); }
    }
    【sp3946】kth num //主席树:查询区间kth

    T9:【p3567】KUR

    • 每次询问区间内有没有数的出现次数超过一半。

    对于编号区间(l,r),对(1,n)的权值范围进行二分;

    若左区间个数<=(r-l+1)/2,则去右区间...

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    //【p3567】KUR //每次询问区间内有没有数的出现次数超过一半
    
    // 对于编号区间(l,r),对(1,n)的权值范围进行二分,若左区间个数<=(r-l+1)/2,则去右区间...
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fx; //正负号
    }
    
    const int N=500019;
    
    int node_cnt,n,m,sum[N*30],rt[N],lc[N*30],rc[N*30];
    
    int a[N],b[N],p; //原序列和离散序列,p:此点在总体中的排名
    
    void build(int &rt,int l,int r){ rt=++node_cnt; if(l==r) return;
      int mid=(l+r)>>1; build(lc[rt],l,mid),build(rc[rt],mid+1,r); }
    
    // ↑↑ 注意动态开点的建树方法,递归建立节点 lc[rt]、rc[rt]。
    
    int modify(int las_,int l,int r){ //在las_的基础上,新建一条链now_
        int now_=++node_cnt; //每次都要开新节点,复制已经记录的下层的位置
        lc[now_]=lc[las_],rc[now_]=rc[las_],sum[now_]=sum[las_]+1;
        if(l==r) return now_; //已经递归到新树(链)的根
        int mid=(l+r)>>1; //↓↓按左右子树不同,分类复制上次的位置
        if(p<=mid) lc[now_]=modify(lc[now_],l,mid);
        else rc[now_]=modify(rc[now_],mid+1,r); return now_; 
        //递归,处理得出此链上每个节点的左右儿子↑↑ 最终在主程序中返回rt[i]的编号
    }
    
    int query(int u,int v,int l,int r,int len){ //查询区间中是否存在len个相同数字
        if(l==r) return l; //走到(代表权值的)叶子节点,此时的权值就是答案(此时的l、r是数值)
        int mid=((l+r)>>1),x=sum[lc[v]]-sum[lc[u]],y=sum[rc[v]]-sum[rc[u]]; 
        // x:左边的总个数(差分求值); y:右边的总个数 。(即:数值在范围内的数的个数)
        if(x>len) return query(lc[u],lc[v],l,mid,len); //因为是按数值划分的,左右区间不会相交
        else if(y>len) return query(rc[u],rc[v],mid+1,r,len); else return 0;
    }
    
    int main(){
        int bn_,ans; reads(n),reads(m);
        for(int i=1;i<=n;i++) reads(a[i]),b[i]=a[i];
        sort(b+1, b+n+1); bn_=unique(b+1,b+n+1)-b-1;
        build(rt[0],1,bn_); //初始空树(便于线段树合并)
        for(int i=1;i<=n;i++){
            p=lower_bound(b+1,b+bn_+1,a[i])-b; //二分查找a[i]在b中的排名
            rt[i]=modify(rt[i-1],1,bn_); //在前一棵树的基础上,递归区间(1,bn_),加入一条链
        } while(m--){ int l,r; reads(l),reads(r); int len=(r-l+1)>>1; //需要相同的个数
            ans=query(rt[l-1],rt[r],1,bn_,len); printf("%d
    ",ans); } //bn_:离散化之后的n
    }
    【p3567】KUR //每次询问区间内有没有数的出现次数超过一半

    T10:【p2633】Count On A Tree

    • N个节点的点权树,M个询问(u,v,k):u xor lastans ~ v 之间第K小的点权。

    【思路】对应的路径可以用树上差分表示:sum[l]+sum[r]−sum[lca]−sum[lca_fa]。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    //【p2633】Count On A Tree //主席树维护树上路径 + 树上差分
    
    // N个节点的点权树,M个询问(u,v,k):u xor lastans ~ v 之间第K小的点权。
    
    //【思路】对应的路径可以用树上差分表示:sum[l]+sum[r]−sum[lca]−sum[lca_fa]。
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fx; //正负号
    }
    
    const int N=1000019; struct node{ int nextt,ver; }e[N*2];
    
    int node_cnt,n,m,bn_,tot=1,head[N*2],sum[N*30],rt[N],lc[N*30],rc[N*30];
    
    int siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2],num[N*2];
    
    int a[N],b[N],p; //原序列和离散序列,p:此点在总体中的排名
    
    void add(int x,int y)
     { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }
    
    //-------------------主席树----------------------//
    
    int modify(int las_,int &now_,int l,int r,int x){
        now_=++node_cnt; //每次都要开新节点,复制已经记录的下层的位置
        lc[now_]=lc[las_],rc[now_]=rc[las_],sum[now_]=sum[las_]+1;
        if(l==r) return now_; //已经递归到新树(链)的根
        int mid=(l+r)>>1; //↓↓按左右子树不同,分类复制上次的位置
        if(p<=mid) lc[now_]=modify(lc[las_],lc[now_],l,mid,x);
        else rc[now_]=modify(rc[las_],rc[now_],mid+1,r,x); return now_; 
        //递归,处理得出此链上每个节点的左右儿子↑↑ 最终在主程序中返回rt[i]的编号
    }
    
    int query(int u,int v,int lca,int lca_fa,int l,int r,int k){ //树上路径Kth
        if(l>=r) return l; //走到(代表权值的)叶子节点,此时的权值就是答案
        int mid=(l+r)>>1,x=sum[lc[u]]+sum[lc[v]]-sum[lc[lca]]-sum[lc[lca_fa]]; 
        if(x>=k) return query(lc[u],lc[v],lc[lca],lc[lca_fa],l,mid,k);
        else return query(rc[u],rc[v],rc[lca],rc[lca_fa],mid+1,r,k-x);
    }
    
    //---------------树链剖分(求lca??)-----------------//
    
    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        modify(rt[fa[u]],rt[u],1,bn_,a[u]);
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue; 
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver];
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链在线段树中的位置连续
            top[son[u]]=top[u],dfs2(son[u],u); //更新top值
        } for(ll i=head[u];i;i=e[i].nextt){
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点
        }
    }
    
    int LCA(int x,int y){
        while(top[x]!=top[y]) 
          (dep[top[x]]>=dep[top[y]])?x=fa[top[x]]:y=fa[top[y]];
        return (dep[x]>=dep[y])?y:x; 
    }
    
    //-------------------主程序-----------------------//
    
    int main(){
        int ans; reads(n),reads(m);
        for(int i=1;i<=n;i++) reads(a[i]),b[i]=a[i];
        sort(b+1, b+n+1); bn_=unique(b+1,b+n+1)-b-1;
        for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+bn_+1,a[i])-b;
        for(int i=1,u,v;i<n;i++) reads(u),reads(v),add(u,v),add(v,u);
        dfs1(1,0),top[1]=1,dfs2(1,0); //树链剖分求lca
        while(m--){ int x,y,z,lca; reads(x),reads(y),reads(z); x^=ans,lca=LCA(x,y);
            printf("%d
    ",query(rt[x],rt[y],rt[lca],rt[fa[lca]],1,bn_,z)); }
    }
    【p2633】Count On A Tree //主席树维护树上路径 + 树上差分 (此代码好像出锅了...)

    T11:【p3302】森林

    • Q x y k查询点x到点y路径上所有的权值中,第k小的权值是多少。
    • 此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点。
    • lc x y在点x和点y之间连接一条边。保证完成此操作后,仍然是一片森林。
    • 强制在线。即要按照 x^lastans y^lastans k^lastans 计算。

    【思路】1.查询路径权值第k小; 2.连接两棵树。利用启发式合并(小的接到大的上面)。

         dfs暴力合并,用父节点重建每个节点的主席树,并且更新每个节点的倍增数组(求lca)。

        对于操作1,对应的路径可以用树上差分表示:sum[l]+sum[r]−sum[lca]−sum[lca_fa]。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p3302】森林
    Q x y k查询点x到点y路径上所有的权值中,第k小的权值是多少。
    此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点。
    lc x y在点x和点y之间连接一条边。保证完成此操作后,仍然是一片森林。
    强制在线。即要按照 x^lastans y^lastans k^lastans 计算。*/
    
    /*【思路】1.查询路径权值第k小; 2.连接两棵树。利用启发式合并(小的接到大的上面)。
    dfs暴力合并,用父节点重建每个节点的主席树,并且更新每个节点的倍增数组(求lca)。 
    对应的路径可以用树上差分表示:sum[l]+sum[r]−sum[lca]−sum[lca_fa]。*/
    
    void reads(int &x_){ int fx_=1;x_=0;char ch_=getchar();
      while(ch_<'0'||ch_>'9'){if(ch_=='-')fx_=-1;ch_=getchar();}
      while(ch_>='0'&&ch_<='9'){x_=x_*10+ch_-'0';ch_=getchar();} x_*=fx_; }
    
    const int N=100019,M=5000019;
    
    int n,m,tot,q,bn_,ans,ver[N*4],nextt[N*4],head[N];
    
    int a[N],fa[N],siz[N],b[N]; //注意这里fa[]记录并查集的联通情况
    
    void add(int u,int v) //双向边
     { ver[++tot]=v,nextt[tot]=head[u],head[u]=tot;
       ver[++tot]=u,nextt[tot]=head[v],head[v]=tot; }
    
    int lc[M],rc[M],sum[M],rt[N],cnt; //主席树sum[]
    
    void update(int las_,int &now_,int l,int r,int x){
        sum[now_=++cnt]=sum[las_]+1; if(l==r) return; int mid=(l+r)>>1;
        if(x<=mid) rc[now_]=rc[las_],update(lc[las_],lc[now_],l,mid,x);
        else lc[now_]=lc[las_],update(rc[las_],rc[now_],mid+1,r,x);
    }
    
    int query(int u,int v,int lca,int lca_fa,int l,int r,int k){
        int mid=(l+r)>>1; if(l>=r) return l; //找到了,返回编号
        int x=sum[lc[v]]+sum[lc[u]]-sum[lc[lca]]-sum[lc[lca_fa]];
        if(x>=k) return query(lc[u],lc[v],lc[lca],lc[lca_fa],l,mid,k);
        else return query(rc[u],rc[v],rc[lca],rc[lca_fa],mid+1,r,k-x);
    }
    
    int find_fa(int x){ return fa[x]==x?x:fa[x]=find_fa(fa[x]); }
    
    int f[N][17],dep[N],vis[N]; //vis数组用于初始建立森林
    
    inline int get_rank(int x){ return lower_bound(b+1,b+bn_+1,x)-b; }
    
    void dfs(int u,int fa_,int root){ //启发式合并,全部合并到root为根的树上
        f[u][0]=fa_; for(int i=1;i<=16;i++) f[u][i]=f[f[u][i-1]][i-1];
        siz[root]++,dep[u]=dep[fa_]+1,fa[u]=root,vis[u]=1;
        update(rt[fa_],rt[u],1,bn_,get_rank(a[u])); //找到u在新树上的新排名
        for(int i=head[u];i;i=nextt[i]) if(ver[i]!=fa_) dfs(ver[i],u,root);
    }
    
    int LCA(int x,int y){
        if(x==y) return x; if(dep[x]<dep[y]) swap(x,y);
        for(int i=16;i>=0;i--) if(dep[f[x][i]]>=dep[y]) x=f[x][i];
        if(x==y) return x; for(int i=16;i>=0;i--) if(f[x][i]!=f[y][i]) 
            x=f[x][i],y=f[y][i]; return f[x][0]; //倍增求lca
    }
    
    int main(){
        int t; reads(t),reads(n),reads(m),reads(q); //t:当前测试组编号,无用
        for(int i=1;i<=n;i++) reads(a[i]),b[i]=a[i],fa[i]=i;
        sort(b+1,b+1+n); bn_=unique(b+1,b+1+n)-b-1; //离散化
        for(int i=1,u,v;i<=m;i++) reads(u),reads(v),add(u,v);
        for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,0,i); //建出森林
        while(q--){ char ch[19]; int x,y,k; cin>>ch,reads(x),reads(y),x=x^ans,y=y^ans;
            if(ch[0]=='Q'){ reads(k),k=k^ans; int lca=LCA(x,y);
              ans=b[query(rt[x],rt[y],rt[lca],rt[f[lca][0]],1,bn_,k)];
              cout<<ans<<endl; } // ↑↑ f[lca][0]=fa_lca,在区间内找第k小的数 
            else{ add(x,y); int fx=find_fa(x),fy=find_fa(y); //启发式:小的合并到大的上
              if(siz[fx]<siz[fy]) swap(x,y),swap(fx,fy); dfs(y,x,fx); } //dfs暴力合并
        }
    }
    【p3302】森林 // 主席树差分维护树上路径 + 启发式合并 (有点bug)

    其中启发式合并的代码:

    inline int get_rank(int x){ return lower_bound(b+1,b+bn_+1,x)-b; }
    
    void dfs(int u,int fa_,int root){ //启发式合并,全部合并到root为根的树上
        f[u][0]=fa_; for(int i=1;i<=16;i++) f[u][i]=f[f[u][i-1]][i-1];
        siz[root]++,dep[u]=dep[fa_]+1,fa[u]=root;
        update(rt[fa_],rt[u],1,bn_,get_rank(a[u])); //找到u在新树上的新排名
        for(int i=head[u];i;i=nextt[i]) if(ver[i]!=fa_) dfs(ver[i],u,root);
    }

    T12:【p1600】天天爱跑步

    将每个人的跑步路线拆成两段路径:s->lca,lca->t。

    对于第一段经过的点,当前这个人能对它产生贡献当且仅当dep[s]-dep[i]==w[i];

    对于第二段路径同理:能产生贡献当且仅当dep[t]-dep[i]==dis(s,t)-w[i]。

    同时要用动态开点线段树维护,判断lca有没有被算重。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    //【p1600】天天爱跑步
    
    /*【分析】将每个人的跑步路线拆成两段路径:s->lca,lca->t。
    对于第一段经过的点,当前这个人能对它产生贡献当且仅当dep[s]-dep[i]==w[i];
    对于第二段路径同理:能产生贡献当且仅当dep[t]-dep[i]==dis(s,t)-w[i]。
    同时要用动态开点线段树维护,判断lca有没有被算重。*/
    
    void reads(int &x){ //读入优化(正负整数)
        int fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const int N=300019; int n,m;
    
    int son[N],num[N],ans[N],head[N],top[N],dep[N],fa[N],siz[N]; 
    
    struct Node{int l,r,sum;}T[N*30]; struct node{int ver,nextt;}e[N<<1]; 
    
    int s[N],t[N],w[N],lca[N],dfn=0,tot=0,cnt=0,rt[N*3]; 
    
    inline void add(int u,int v){e[++cnt].ver=v,e[cnt].nextt=head[u],head[u]=cnt;}
    
    inline void update(int&p,int l,int r,int k,int v){ if(!p) p=++tot,T[p].l=T[p].r=T[p].sum=0; 
      T[p].sum+=v; if(l==r) return; int mid=l+r>>1; //动态开点
      if(k<=mid) update(T[p].l,l,mid,k,v); else update(T[p].r,mid+1,r,k,v); } 
    
    inline int query(int p,int l,int r,int ql,int qr){ if(!p)return 0; 
        if(ql<=l&&r<=qr) return T[p].sum; int mid=l+r>>1; //查询区间sum
        if(qr<=mid) return query(T[p].l,l,mid,ql,qr); if(ql>mid)return query(T[p].r,mid+1,r,ql,qr); 
        return query(T[p].l,l,mid,ql,mid)+query(T[p].r,mid+1,r,mid+1,qr); } 
    
    inline void dfs1(int x){ siz[x]=1,son[x]=0; for(int i=head[x];i;i=e[i].nextt){ 
      int v=e[i].ver; if(v==fa[x]) continue; fa[v]=x,dep[v]=dep[x]+1,dfs1(v),siz[x]+=siz[v]; 
      if(siz[v]>siz[son[x]]) son[x]=v; } } //求fa[],dep[],siz[],son[]
    
    inline void dfs2(int x,int tp){ top[x]=tp,num[x]=++dfn; if(son[x]) dfs2(son[x],tp); 
      for(int i=head[x];i;i=e[i].nextt){ int v=e[i].ver; if(v!=son[x]&&v!=fa[x]) dfs2(v,v); } }
    
    inline int LCA(int x,int y){ while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); 
      x=fa[top[x]]; } return dep[x]<dep[y]?x:y; } //用 轻重链划分 求x,y的lca
    
    int main(){ 
        reads(n),reads(m); for(int i=1,u,v;i<n;++i) reads(u),reads(v),add(u,v),add(v,u);
        for(int i=1;i<=n;++i) reads(w[i]); dfs1(1),dfs2(1,1); //链剖 
        for(int i=1;i<=m;++i){ reads(s[i]),reads(t[i]); lca[i]=LCA(s[i],t[i]); 
            if(dep[s[i]]-dep[lca[i]]==w[lca[i]]) ans[lca[i]]--; 
            update(rt[dep[s[i]]],1,n,num[s[i]],1); //差分
            if(fa[lca[i]]) update(rt[dep[s[i]]],1,n,num[fa[lca[i]]],-1); } 
        for(int i=1;i<=n;++i) ans[i]+=query(rt[w[i]+dep[i]],1,n,num[i],num[i]+siz[i]-1); 
        memset(rt,0,sizeof(rt)),tot=0; for(int i=1;i<=m;++i){ 
            update(rt[n*2+dep[s[i]]-2*dep[lca[i]]],1,n,num[t[i]],1); 
            if(fa[lca[i]]) update(rt[n*2+dep[s[i]]-2*dep[lca[i]]],1,n,num[fa[lca[i]]],-1); } 
        for(int i=1;i<=n;++i) ans[i]+=query(rt[w[i]-dep[i]+n*2],1,n,num[i],num[i]+siz[i]-1); 
        for(int i=1;i<=n;++i) cout<<ans[i]<<' '; return 0; 
    }
    【p1600】天天爱跑步

     T13:【p4215】踩气球

    • 给出一个长度为 n 的序列,序列中每一个数都是正整数。
    • m 个指定区间,q 次操作,每次操作将某个位置的数-1(最多减到0),
    • 并询问有多少个指定区间的区间和为0。强制在线。

    【思路】线段树维护:v[rt].push_back(id);

    即:每个管理/叶子节点存一个vector,记录此点管理的所有位置有关的区间id。

    c[id]++; 记录区间id影响的(受管理的)线段树区间数。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    /*【p4215】踩气球
    给出一个长度为 n 的序列,序列中每一个数都是正整数。
    现在给出 m 个指定区间以及 q 次操作,每次操作将某个位置的数-1(最多减到0),
    并询问有多少个指定区间的区间和为0。强制在线。*/
    
    /*【思路】线段树维护:v[rt].push_back(id);
    即:每个管理/叶子节点存一个vector,记录此点管理的所有位置有关的区间id。
    c[id]++; 记录区间id影响的(受管理的)线段树区间数。 */
    
    void reads(int &x_){ int fx_=1;x_=0;char ch_=getchar();
      while(ch_<'0'||ch_>'9'){if(ch_=='-')fx_=-1;ch_=getchar();}
      while(ch_>='0'&&ch_<='9'){x_=x_*10+ch_-'0';ch_=getchar();} x_*=fx_; }
    
    const int N=100019;
    
    #define lson l,mid,rt<<1
    
    #define rson mid+1,r,rt<<1|1
    
    vector<int> v[N<<2]; ll sum[N<<2]; 
    
    int c[N],ans; // c[]:区间id影响的(受管理的)线段树区间数
    
    void pushup(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; }
    
    void build(int l,int r,int rt){
      if(l==r){ scanf("%lld",&sum[rt]); return; }
      int mid=(l+r)>>1; build(lson),build(rson),pushup(rt); }
    
    void init(int ql,int qr,int id,int l,int r,int rt){
        if(ql<=l&&r<=qr){ v[rt].push_back(id),c[id]++; return; }
        int mid=(l+r)>>1; if(ql<=mid) init(ql,qr,id,lson);
        if(qr>mid) init(ql,qr,id,rson); //把每个区间影响的线段树节点都标上id
    }
    
    void solve(int p,int l,int r,int rt){
        sum[rt]--; if(!sum[rt]){ //正好减到0
            vector<int>::iterator it; //注意每个管理节点都要修改
            for(it=v[rt].begin();it!=v[rt].end();it++)
             { c[*it]--; if(!c[*it]) ans++; } 
        } if(l==r) return; int mid=(l+r)>>1;
        if(p<=mid) solve(p,lson); else solve(p,rson);
    }
    
    int main(){
        int n,m,q,x,y; reads(n),reads(m); build(1,n,1);
        for(int i=1;i<=m;i++) reads(x),reads(y),init(x,y,i,1,n,1);
        reads(q); while(q--) reads(x), //单点-1
            solve((x+ans-1)%n+1,1,n,1),printf("%d
    ",ans);
    }
    【p4215】踩气球 // 线段树 + vector

    T14:【p1471】方差

    • 操作1:1 x y k,表示将第x到第y项每项加k。
    • 操作2:2 x y,表示求出第x到第y项的平均数。
    • 操作3:3 x y,表示求出第x到第y项的方差。

    【分析】方差的公式可以转化为:(a1^2+a2^2+...+an^2)/n-平均数^2。

    所以只需要维护,区间平方和 和 区间和 即可。

    修改操作:a1^2+a2^2+...+an^2 --> (a1+x)^2+(a2+x)^2+...+(an+x)^2

    即:a1^2+a2^2+...+an^2 + n*(x^2) + 2*x*原平均数*n。

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<cassert>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #include<deque>
    using namespace std;
    typedef long long ll;
    
    /*【p1471】方差
    操作1:1 x y k,表示将第x到第y项每项加上k,k为一实数。
    操作2:2 x y,表示求出第x到第y项这一子数列的平均数。
    操作3:3 x y,表示求出第x到第y项这一子数列的方差。*/
    
    /*【分析】方差的公式可以转化为:(a1^2+a2^2+...+an^2)/n-平均数^2。
    所以只需要维护,区间平方和 和 区间和 即可。
    修改操作:a1^2+a2^2+...+an^2 --> (a1+x)^2+(a2+x)^2+...+(an+x)^2
    即:a1^2+a2^2+...+an^2 + n*(x^2) + 2*x*原平均数*n。 */
    
    struct node{ double a,b,tag; }tree[1500019];
    
    void PushUp(int rt){ //向上求出管理节点
        tree[rt].a=tree[rt<<1].a+tree[rt<<1|1].a;
        tree[rt].b=tree[rt<<1].b+tree[rt<<1|1].b;
    } //维护 区间所有数的和 和 区间所有数的平方和
    
    void build(int l,int r,int rt){ //建立线段树
        if(l==r){ cin>>tree[rt].a; //叶子节点
            tree[rt].b=tree[rt].a*tree[rt].a; return;
        } int mid=(l+r)>>1; //递归左右儿子
        build(l,mid,rt<<1),build(mid+1,r,rt<<1|1);
        PushUp(rt); //标记上移
    }
    
    void PushDown(int rt,int len){
        if(tree[rt].tag){ //标记每次下移一层
            tree[rt<<1].b+=2*tree[rt].tag*tree[rt<<1].a
                +(len-len/2)*tree[rt].tag*tree[rt].tag;
            tree[rt<<1|1].b+=2*tree[rt].tag*tree[rt<<1|1].a
                +(len/2)*tree[rt].tag*tree[rt].tag;
            tree[rt<<1].a+=(len-len/2)*tree[rt].tag;
            tree[rt<<1|1].a+=(len/2)*tree[rt].tag;
            tree[rt<<1].tag+=tree[rt].tag;
            tree[rt<<1|1].tag+=tree[rt].tag;
            tree[rt].tag=0; //标记下移,并清空原标记
        }
    }
    
    void update(int x,int y,double k,int l,int r,int rt){
        if(x<=l&&r<=y){ tree[rt].tag+=k;
            tree[rt].b+=2*k*tree[rt].a+k*k*(r-l+1),
            tree[rt].a+=(r-l+1)*k; return; 
        } PushDown(rt,r-l+1); //标记下移
        int mid=(l+r)>>1; //↓↓判断此时左右区间是否和原区间有交集
        if(mid>=x) update(x,y,k,l,mid,rt<<1);
        if(mid<y) update(x,y,k,mid+1,r,rt<<1|1);
        PushUp(rt); //修改这条线路上的sum值
    }
    
    double query_1(int x,int y,int l,int r,int rt){
        if(x<=l&&r<=y) return tree[rt].a;
        PushDown(rt,r-l+1); //标记下移
        int mid=(l+r)>>1; double sums=0;
        if(mid>=x) sums+=query_1(x,y,l,mid,rt<<1);
        if(mid<y) sums+=query_1(x,y,mid+1,r,rt<<1|1);
        return sums; //求区间和
    }
    
    double query_2(int x,int y,int l,int r,int rt){
        if(x<=l&&r<=y) return tree[rt].b;
        PushDown(rt,r-l+1); //标记下移
        int mid=(l+r)>>1; double sums=0;
        if(mid>=x) sums+=query_2(x,y,l,mid,rt<<1);
        if(mid<y) sums+=query_2(x,y,mid+1,r,rt<<1|1);
        return sums; //求区间平方和
    }
    
    int main(/*hs_love_wjy*/){
        int n,m,op,x,y; double k; 
        scanf("%d%d",&n,&m);
        build(1,n,1); //建树,在建树时输入原数组值
        for(int i=1;i<=m;i++){
            scanf("%d",&op); //操作编号
            if(op==1) scanf("%d%d%lf",&x,&y,&k),
                update(x,y,k,1,n,1); //区间(x,y)+k
            if(op==2) scanf("%d%d",&x,&y), //区间平均数
                printf("%.4lf
    ",query_1(x,y,1,n,1)/(y-x+1));
            if(op==3){ scanf("%d%d",&x,&y);
                double cnt_b=query_2(x,y,1,n,1)/(y-x+1),
                       cnt_a=query_1(x,y,1,n,1)/(y-x+1);
                cnt_b=cnt_b-cnt_a*cnt_a; //由公式转化而来
                printf("%.4lf
    ",cnt_b);
            }
        }
    }
    【p1471】方差

    T15:【p2253】好一个一中腰鼓

    • 一个01串,每次要求对某个点进行异或修改,
    • 求每次修改后的最长连续(0和1交错出现)序列长度。

    l代表从线段树管理节点维护的左端点开始,向右的最大01序列的长度。

    r代表从线段树管理节点维护的右端点开始,向左的最大01序列的长度。

    ans代表整个区间最大的01序列(可以不含左,右端点)

    lk代表区间左端点的颜色,rk代表区间右端点的颜色。

    那么,区间上传(pushup)应该这样写:

    void PushUp(int rt){ //向上求出管理节点
        tree[rt].l=tree[rt<<1].l,tree[rt].r=tree[rt<<1|1].r;
        tree[rt].lk=tree[rt<<1].lk,tree[rt].rk=tree[rt<<1|1].rk;
        tree[rt].ans=max(tree[rt<<1].r,tree[rt<<1].ans);
        tree[rt].ans=max(tree[rt<<1|1].l,tree[rt].ans);
        tree[rt].ans=max(tree[rt].ans,tree[rt<<1|1].ans);
        if(tree[rt<<1].rk!=tree[rt<<1|1].lk){ //中间两节点可以合并,序列合并
            tree[rt].ans=max(tree[rt<<1].r+tree[rt<<1|1].l,tree[rt].ans);
            if(tree[rt<<1].l==tree[rt<<1].len)
                tree[rt].l+=tree[rt<<1|1].l;
            if(tree[rt<<1|1].r==tree[rt<<1|1].len)
                tree[rt].r+=tree[rt<<1].r;
        }
    }
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<cassert>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #include<deque>
    using namespace std;
    typedef long long ll;
    
    //【p2253】好一个一中腰鼓(初中的回忆啊qwq)
    
    //一个01串,每次要求对某个点进行异或修改,
    //求每次修改后的最长连续(0和1交错出现)序列长度。
    
    //l代表从线段树管理节点维护的左端点开始,向右的最大01序列的长度
    //r代表从线段树管理节点维护的右端点开始,向左的最大01序列的长度
    //ans代表整个区间最大的01序列(可以不含左,右端点)
    //lk代表区间左端点的颜色,rk代表区间右端点的颜色
    
    struct node{ int l,r,lk,rk,ans,len; }tree[1500019];
    
    void PushUp(int rt){ //向上求出管理节点
        tree[rt].l=tree[rt<<1].l,tree[rt].r=tree[rt<<1|1].r;
        tree[rt].lk=tree[rt<<1].lk,tree[rt].rk=tree[rt<<1|1].rk;
        tree[rt].ans=max(tree[rt<<1].r,tree[rt<<1].ans);
        tree[rt].ans=max(tree[rt<<1|1].l,tree[rt].ans);
        tree[rt].ans=max(tree[rt].ans,tree[rt<<1|1].ans); //两边的两方向的ans值
        if(tree[rt<<1].rk!=tree[rt<<1|1].lk){ //中间两节点可以合并,序列合并
            tree[rt].ans=max(tree[rt<<1].r+tree[rt<<1|1].l,tree[rt].ans);
            if(tree[rt<<1].l==tree[rt<<1].len) //左儿子节点的维护的整个区间都是可行序列
                tree[rt].l+=tree[rt<<1|1].l; //从rt维护的左端点开始的最长01序列可以合并右边
            if(tree[rt<<1|1].r==tree[rt<<1|1].len) //右儿子节点的维护的整个区间都是可行序列
                tree[rt].r+=tree[rt<<1].r; //从rt维护的右端点开始的最长01序列可以合并左边
        }
    }
    
    void build(int l,int r,int rt){ //建立线段树
        tree[rt].len=r-l+1;
        if(l==r){ //叶子节点
            tree[rt].l=tree[rt].r=tree[rt].ans=1;
            tree[rt].lk=tree[rt].rk=0; return;
        } //↑↑相当于把a数组的值赋到线段树的求和数组中
        int mid=(l+r)>>1;
        build(l,mid,rt<<1); //左儿子编号2*rt
        build(mid+1,r,rt<<1|1); //右儿子编号2*rt+1
        PushUp(rt);
    }
     
    void update(int p,int l,int r,int rt){ //单点修改
        if(l==r){ tree[rt].lk=tree[rt].rk=tree[rt].lk^1; return; }
        int mid=(l+r)>>1; //↓↓此时p在区间左半边
        if(p<=mid) update(p,l,mid,rt<<1); //递归左儿子寻找p
        else update(p,mid+1,r,rt<<1|1); //递归右儿子寻找p
        PushUp(rt); //修改这条线路上的sum值
    }
    
    int main(){
        int n,m; scanf("%d%d",&n,&m);
        build(1,n,1); //建树并求和
        for(int i=1;i<=m;i++){
            scanf("%d",&x),update(x,1,n,1);
            printf("%d
    ",tree[1].ans);
        } //在根节点统计全局的01序列最大长度
    }
    【p2253】好一个一中腰鼓 //维护整体区间

    T16:【CF438D】

    • 给定数列,区间查询和,区间取模,单点修改。

    利用 ‘ 区间max<模数时,整个区间都不用取模 ’ 性质优化暴力时间。

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<cassert>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #include<deque>
    using namespace std;
    typedef long long ll;
    
    //【CF438D】给定数列,区间查询和,区间取模,单点修改。
    
    // 利用‘区间max<模数时,整个区间都不用取模’性质优化暴力时间。
    
    #define N 100019
    
    #define ls rt<<1
    #define rs rt<<1|1
    
    int n,m,maxx[N<<2]; ll sum[N<<2];
    
    inline int reads(){ char c=getchar();int num=0,f=1;
        for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
        for(;isdigit(c);c=getchar()) num=num*10+c-'0'; return num*f; }
    
    inline void pushup(int rt)
     { sum[rt]=sum[ls]+sum[rs],maxx[rt]=max(maxx[ls],maxx[rs]); }
    
    void build(int rt, int l, int r){
        if(l==r){ maxx[rt]=sum[rt]=reads(); return; }
        int mid=(l+r)>>1; build(ls,l,mid),build(rs,mid+1,r),pushup(rt); }
    
    void add(int rt, int l, int r, int p, int val) {
        if(l==r){ maxx[rt]=sum[rt]=val; return; }
        int mid=(l+r)>>1; if(p<=mid) add(ls,l,mid,p,val);
        else add(rs,mid+1,r,p,val); pushup(rt); }
    
    ll csum(int rt, int l, int r, int L, int R) {
        if(L<=l&&r<=R) return sum[rt];
        int mid=(l+r)>>1; ll ans=0;
        if(L<=mid) ans+=csum(ls,l,mid,L,R);
        if(R>mid) ans+=csum(rs,mid+1,r,L,R); return ans; } //区间求和
    
    void modify(int rt,int l,int r,int L,int R,int p) {
        if(maxx[rt]<p) return; //区间max<模数,整个区间都不用取模
        if(l==r){ sum[rt]%=p;maxx[rt]%=p; return; }
        int mid=(l+r)>>1; //↑↑找到区间的所有叶子节点,暴力更新
        if(L<=mid) modify(ls,l,mid,L,R,p); if(R>mid) modify(rs,mid+1,r,L,R,p); pushup(rt); }
    
    int main(){
        n=reads(),m=reads(); build(1,1,n);
        for(int i=1,k,x,y,z;i<=m;i++){
            k=reads(),x=reads(),y=reads();
            if(k==1) printf("%lld
    ",csum(1,1,n,x,y));
            if(k==2) z=reads(),modify(1,1,n,x,y,z);
            if(k==3) add(1,1,n,x,y);
        }
    }
    【CF438D】给定数列,区间查询和,区间取模,单点修改。

    T17:【p2073】送花

    • 1 X C 添加一朵美丽值为x,价格为c的花。
    • (如果加入花朵的价格与已有花朵 ‘ 重复 ’ 则不能加入。)
    • 2 删除最便宜的一朵花。3 删除最贵的一朵花。
    • (若删除操作时没有花,则跳过删除操作。)
    • -1 开始包装花束,输出所有花的美丽值的总和 和 总价格。

    以价格c为下标建立线段树。 原来这个就叫“权值线段树”???是我孤陋寡闻...

    对于每个新的花,在线段树的c处添加美丽值为x;删除操作就是清零。

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<cassert>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #include<deque>
    using namespace std;
    typedef long long ll;
    
    /*【p2073】送花
    1 X C 添加一朵美丽值为x,价格为c的花。
    (如果加入花朵的价格与已有花朵【重复】则不能加入。)
    2 删除最便宜的一朵花。3 删除最贵的一朵花。
    (若删除操作时没有花,则跳过删除操作。)
    -1 开始包装花束,输出所有花的美丽值的总和 和 总价格。*/
    
    int n=1000019,op,xi,ci;
    
    struct node{ int x,c; }tree[5000019];
    
    void PushUp(int rt){ //向上求出管理节点
        tree[rt].x=tree[rt<<1].x+tree[rt<<1|1].x;
        tree[rt].c=tree[rt<<1].c+tree[rt<<1|1].c;
    } //维护 区间美观值的和 和 区间价格的和
    
    void add(int p,int x,int c,int l,int r,int rt){
        if(l==r){ if(tree[rt].c) return; //价格重复不能加入
            tree[rt].c=c,tree[rt].x=x; return;
        } int mid=(l+r)>>1; //↓↓寻找p(即c值)
        if(p<=mid) add(p,x,c,l,mid,rt<<1);
        else add(p,x,c,mid+1,r,rt<<1|1);
        PushUp(rt); //修改这条线路上的sum值
    }
    
    void del(int l,int r,int rt,int flag){ //按下标的单调性确定min/max值
    //↑↑因为是按照价格从小到大作为下标存的,所以只需要寻找该位置有没有值
        if(l==r){ tree[rt].x=tree[rt].c=0; return; }
        int mid=(l+r)>>1; //选择性递归左右子树
        if(flag&&tree[rt<<1].c&&tree[rt<<1|1].c) del(mid+1,r,rt<<1|1,flag);
        else if(!tree[rt<<1].c) del(mid+1,r,rt<<1|1,flag);
        else del(l,mid,rt<<1,flag); //求max/min值
        PushUp(rt); //状态上移
    }
    
    int main(/*hs_love_wjy*/){
        while(scanf("%d",&op)&&op!=-1){
            if(op==1) scanf("%d%d",&xi,&ci),add(ci,xi,ci,1,n,1);
            if(op==2) del(1,n,1,1); if(op==3) del(1,n,1,0);
        } cout<<tree[1].x<<" "<<tree[1].c<<endl;
    }
    【p2073】送花

    T18:【p4513】小白逛公园

    • 维护一个动态的带修改最大子段和。

    此最大子段和可以通过分类讨论,将两个区间合并为一,从而用线段树维护。

    即:用tot表示区间数总和,lm表示区间左端点开始的最大子段和,

    rm表示区间右端点开始的最大子段和,ans表示整个区间的最大子段和。

    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    /*【p4513】小白逛公园 
    维护一个动态的带修改最大子段和。*/
    
    //此最大子段和可以通过分类讨论,将两个区间合并为一,从而用线段树维护。
    //即:用tot表示区间数总和,lm表示区间左端点开始的最大子段和,
    //rm表示区间右端点开始的最大子段和,ans表示整个区间的最大子段和。
    
    void reads(int &x){ //读入优化(正负整数)
        int f=1;x=0;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        x*=f; //正负号
    }
    
    struct Node{ int l,r,lm,rm,ans,tot; }tree[4000005];
    
    //记录总和tot:用于更新包含区间左右的区间最大子段和(lm、rm)
    
    int n,m,op,x,y,cnt=0,a[500001];
    
    void PushUp(int rt){ //合并答案,向上传递
        int ll=rt<<1,rr=rt<<1|1; tree[rt].tot=tree[ll].tot+tree[rr].tot;
        tree[rt].lm=max(tree[ll].lm,tree[ll].tot+tree[rr].lm);
        tree[rt].rm=max(tree[rr].rm,tree[rr].tot+tree[ll].rm);
        tree[rt].ans=max(max(tree[ll].ans,tree[rr].ans),tree[ll].rm+tree[rr].lm);
    }
    
    void build(int l,int r,int rt){
        tree[rt].l=l,tree[rt].r=r;
        if(l==r){ tree[rt].lm=tree[rt].rm=tree[rt].tot=a[l];
            tree[rt].ans=a[l]; return; }
        int mid=(l+r)>>1; build(l,mid,rt<<1),build(mid+1,r,rt<<1|1);
        PushUp(rt); //合并答案,向上传递
    }
    
    void update(int rt,int p,int num){
        int x=tree[rt].l,y=tree[rt].r,mid=(x+y)>>1;
        if(x==y){ tree[rt].lm=tree[rt].rm=tree[rt].tot=num;
            tree[rt].ans=num; return; //到达相应叶子节点,进行修改
        } if(p<=mid) update(rt<<1,p,num);
          else update(rt<<1|1,p,num); PushUp(rt);
    }
    
    Node query(int rt,int l,int r){
        int x=tree[rt].l,y=tree[rt].r;
        if(l<=x&&r>=y) return tree[rt]; //完全包含rt管理的区间
        int mid=(x+y)>>1,ll=rt<<1,rr=rt<<1|1;
        if(r<=mid) return query(ll,l,r);
        else if(l>mid) return query(rr,l,r);
        else{ //询问区间跨过mid时要合并答案
            Node t,t1=query(ll,l,r),t2=query(rr,l,r);
            t.lm=max(t1.lm,t1.tot+t2.lm);
            t.rm=max(t2.rm,t2.tot+t1.rm);
            t.ans=max(max(t1.ans,t2.ans),t1.rm+t2.lm);
            return t; //返回Node编号
        }
    }
    
    int main(){
        reads(n),reads(m); for(int i=1;i<=n;i++) reads(a[i]);
        build(1,n,1); //初始建树
        for(int i=1;i<=m;i++){
            reads(op),reads(x),reads(y);
            if(op==1){ if(x>y) swap(x,y);
                printf("%d
    ",query(1,x,y).ans);
            } else update(1,x,y);
        }
    }
    【p4513】小白逛公园 // 动态 带修改 的最大子段和

    T19:【SP1043】GSS1

    • 求区间的最大子段和。和上一题完全一样。
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    /*【SP1043】GSS1 // 最大子段和 */
    
    //此最大子段和可以通过分类讨论,将两个区间合并为一,从而用线段树维护。
    //即:用tot表示区间数总和,lm表示区间左端点开始的最大子段和,
    //rm表示区间右端点开始的最大子段和,ans表示整个区间的最大子段和。
    
    void reads(int &x){ //读入优化(正负整数)
        int f=1;x=0;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        x*=f; //正负号
    }
    
    struct Node{ int l,r,lm,rm,ans,tot; }tree[4000005];
    
    //记录总和tot:用于更新包含区间左右的区间最大子段和(lm、rm)
    
    int n,m,op,x,y,cnt=0,a[500001];
    
    void PushUp(int rt){ //合并答案,向上传递
        int ll=rt<<1,rr=rt<<1|1; tree[rt].tot=tree[ll].tot+tree[rr].tot;
        tree[rt].lm=max(tree[ll].lm,tree[ll].tot+tree[rr].lm);
        tree[rt].rm=max(tree[rr].rm,tree[rr].tot+tree[ll].rm);
        tree[rt].ans=max(max(tree[ll].ans,tree[rr].ans),tree[ll].rm+tree[rr].lm);
    }
    
    void build(int l,int r,int rt){
        tree[rt].l=l,tree[rt].r=r;
        if(l==r){ tree[rt].lm=tree[rt].rm=tree[rt].tot=a[l];
            tree[rt].ans=a[l]; return; }
        int mid=(l+r)>>1; build(l,mid,rt<<1),build(mid+1,r,rt<<1|1);
        PushUp(rt); //合并答案,向上传递
    }
    
    void update(int rt,int p,int num){
        int x=tree[rt].l,y=tree[rt].r,mid=(x+y)>>1;
        if(x==y){ tree[rt].lm=tree[rt].rm=tree[rt].tot=num;
            tree[rt].ans=num; return; //到达相应叶子节点,进行修改
        } if(p<=mid) update(rt<<1,p,num);
          else update(rt<<1|1,p,num); PushUp(rt);
    }
    
    Node query(int rt,int l,int r){
        int x=tree[rt].l,y=tree[rt].r;
        if(l<=x&&r>=y) return tree[rt]; //完全包含rt管理的区间
        int mid=(x+y)>>1,ll=rt<<1,rr=rt<<1|1;
        if(r<=mid) return query(ll,l,r);
        else if(l>mid) return query(rr,l,r);
        else{ //询问区间跨过mid时要合并答案
            Node t,t1=query(ll,l,r),t2=query(rr,l,r);
            t.lm=max(t1.lm,t1.tot+t2.lm);
            t.rm=max(t2.rm,t2.tot+t1.rm);
            t.ans=max(max(t1.ans,t2.ans),t1.rm+t2.lm);
            return t; //返回Node编号
        }
    }
    
    int main(){
        reads(n); for(int i=1;i<=n;i++) reads(a[i]); 
        reads(m); build(1,n,1); //初始建树
        for(int i=1;i<=m;i++) reads(x),reads(y),
            printf("%d
    ",query(1,x,y).ans);
    }
    【SP1043】GSS1 // 最大子段和

    T20:

    ——时间划过风的轨迹,那个少年,还在等你

  • 相关阅读:
    python:利用asyncio进行快速抓取
    os.path.exists(path) 和 os.path.lexists(path) 的区别
    isdigit()判断是不是数字
    switf资源
    51cto培训课程
    51cto运维培训课程
    Python: 在Unicode和普通字符串之间转换
    VC++ CopyFile函数使用方法
    Eclipse断点调试
    AFNetworking2.0后 进行Post请求
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10558800.html
Copyright © 2011-2022 走看看