【浮*光】#数据结构# 数据结构の相关练习题

 

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

 

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:\n",++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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",query(1,x,y).ans);
}
【SP1043】GSS1 // 最大子段和

 


 

 

T20:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

posted @ 2019-03-21 20:04  花神&缘浅flora  阅读(278)  评论(0编辑  收藏  举报