线段树+树链剖分全纪录

线段树

最基础线段树,就是支持区间加乘,查询区间最值
总的来说,就是可以在区间上进行操作

(给出的可能不是完整代码,不过都是精华)
代码中有几个细节需要注意:

  • 默认先乘后加,在计算值的时候统一表达式:
    t[bh].sum=( t[bh].sum*t[bh].mul%p + (t[bh].y-t[bh].x+1)*t[bh].ad%p )%p;

  • 永远记住:先维护值,再下传标记

  • 标记下传: son.mul=son.mulfa.mul,son.ad=son.adfa.mul+fa.ad s o n . m u l = s o n . m u l ∗ f a . m u l , s o n . a d = s o n . a d ∗ f a . m u l + f a . a d

  • build的时候注意初始化

  • 虽然我们在push的时候遇到叶节点就返回,不过也要把叶节点上的标记情况,防止冲突:
    t[bh].ad=0; t[bh].mul=1; return;

虽然理论上线段树的空间是2*n,而且daloa们表示push的时候特判一下(叶节点不push),空间是可以缩小到2*n的,但是保险起见还是4倍数组不要投机取巧

const int N=100010;
struct node{
    int x,y;
    ll sum,ad,mul;
};
node t[N<<2];
ll a[N],p=100;

void update(int bh) {
    t[bh].sum=(t[bh<<1].sum+t[bh<<1|1].sum)%p;
}

void push(int bh) {
    if (t[bh].x==t[bh].y) {
        t[bh].ad=0; t[bh].mul=1;
        return;      
    }        
    int lc=bh<<1,rc=bh<<1|1;
    if (t[bh].ad!=0||t[bh].mul!=1) {       //默认乘操作在加操作之前 
        //永远记住:先维护值,再下传标记 
        t[lc].sum=( (t[lc].sum*t[bh].mul)%p + ((t[lc].y-t[lc].x+1)%p*t[bh].ad)%p )%p; 
        t[lc].mul=(t[lc].mul*t[bh].mul)%p;
        t[lc].ad=( (t[lc].ad*t[bh].mul)%p+t[bh].ad )%p;

        t[rc].sum=( (t[rc].sum*t[bh].mul)%p + ((t[rc].y-t[rc].x+1)%p*t[bh].ad)%p )%p; 
        t[rc].mul=(t[rc].mul*t[bh].mul)%p;
        t[rc].ad=( (t[rc].ad*t[bh].mul)%p+t[bh].ad )%p;

        t[bh].ad=0; t[bh].mul=1;
    }
}

void build(int bh,int l,int r) {
    t[bh].x=l; t[bh].y=r;
    t[bh].sum=0;
    t[bh].mul=1; t[bh].ad=0;
    if (l==r) {
        t[bh].sum=a[l]%p;
        return;
    }
    int mid=(l+r)>>1;
    build(bh<<1,l,mid);
    build(bh<<1|1,mid+1,r);
    update(bh);
}

void add(int bh,int l,int r,int L,int R,ll z) {     //[L,R] 
    push(bh);
    if (l>=L&&r<=R) {
        t[bh].ad+=z; t[bh].ad%=p;
        t[bh].sum=( t[bh].sum*t[bh].mul%p + (t[bh].y-t[bh].x+1)*t[bh].ad%p )%p;
        return;
    }
    int mid=(l+r)>>1;
    if (L<=mid) add(bh<<1,l,mid,L,R,z);
    if (R>mid) add(bh<<1|1,mid+1,r,L,R,z);
    update(bh);
}

void multi(int bh,int l,int r,int L,int R,ll z) {
    push(bh);
    if (l>=L&&r<=R) {
        t[bh].mul*=z; t[bh].mul%=p;
        t[bh].sum=( t[bh].sum*t[bh].mul%p + (t[bh].y-t[bh].x+1)*t[bh].ad%p )%p;
        return;
    }
    int mid=(l+r)>>1;
    if (L<=mid) multi(bh<<1,l,mid,L,R,z);
    if (R>mid) multi(bh<<1|1,mid+1,r,L,R,z);
    update(bh);
}

ll ask(int bh,int l,int r,int L,int R) {
    push(bh);
    if (l>=L&&r<=R) return t[bh].sum%p;
    int mid=(l+r)>>1;
    ll ans=0;
    if (L<=mid) ans=(ans+ask(bh<<1,l,mid,L,R))%p;
    if (R>mid) ans=(ans+ask(bh<<1|1,mid+1,r,L,R))%p;
    return ans%p;
} 

当然,线段树也可以维护简单的图上的信息(并不是说信息简单,只是图简单而已)
经典例题:线段树维护MST或图的连通性


进阶一点,我们可以用线段树维护二维平面上的线段,从而回答某个点上的最高线段或最低线段
超哥线段树
这种线段树最主要的思想就是:标记永久化
所以在查询的时候,只要一个区间有线段标记,我们就要维护一下
重点还是insert

struct node{
    double a,b;
    bool flag;
};

void insert(int bh,int l,int r,int L,int R,double a,double b) {
    if (l>r) return;
    int mid=(l+r)>>1;
    if (l>=L&&r<=R) {
        if (!t[bh].flag) {
            t[bh].a=a; t[bh].b=b; t[bh].flag=1;
            return;
        }

        double z1=(double)l*t[bh].a+t[bh].b;     //原线段 
        double z2=(double)r*t[bh].a+t[bh].b;
        double z3=(double)l*a+b;                 //新线段 
        double z4=(double)r*a+b;

        //维护最高点 
        if (z1>=z3&&z2>=z4) return;
        if (z1<=z3&&z2<=z4) {
            t[bh].a=a; t[bh].b=b;
            return;
        }
        double x=(b-t[bh].b)/(t[bh].a-a);
        if (x<=mid) {                            //交点在左区间
            if (z3>z1) insert(bh<<1,l,mid,L,R,a,b);
            //新线段在左区间更优
            else {
                insert(bh<<1,l,mid,L,R,t[bh].a,t[bh].a);
                //原线段在左区间更优
                t[bh].a=a; t[bh].b=b;    
                //标记永久化 
            } 
        }
        else {                                   //交点在右区间 
            if (z4>z2) insert(bh<<1|1,mid+1,r,L,R,a,b);
            //新线段在右区间更优
            else {
                insert(bh<<1|1,mid+1,r,L,R,t[bh].a,t[bh].b);
                //原线段在右区间更优
                t[bh].a=a; t[bh].b=b;    
                //标记永久化 
            }
        }
    }
    if (L<=mid) insert(bh<<1,l,mid,L,R,a,b);
    if (R>mid) insert(bh<<1|1,mid+1,r,L,R,a,b);
} 

还有线段树和其他算法的经典结合:
经典例题:dp+线段树
经典例题:线段树+并查集


在图论中,可能会有区间连边的情况,这个时候我们就可以用线段树优化建图
一般还是建立两棵线段树:入数,出树
两棵树的编号范围分别为: [1,4n],[4n+1,8n] [ 1 , 4 n ] , [ 4 n + 1 , 8 n ]
注意一下build即可

void build(int bh,int l,int r) {
    if (l==r) {
        add(bh+4*n,bh,0);    //入树->出树  表示可以再次出发 
        pos[l]=bh;           //l在线段树中对应的编号
        return; 
    }
    int mid=(l+r)>>1;
    build(bh<<1,l,mid);
    build(bh<<1|1,mid+1,r);
    int lc=bh<<1,rc=bh<<|1;
    add(lc,bh,0);            //出树:子连父 
    add(rc,bh,0);
    add(bh+4*n,lc+4*n,0);    //入树:父连子 
    add(bh+4*n,rc+4*n,0);
}

线段树模拟费用流,简单来说就是线段树支持查询区间最大子序列和区间取反
比较单一的题型:区间内k次连续选择序列,使得收益最大


二维线段树讲解

以上说的都是一维线段树的情况,举一反三,我们也可以把线段树拓展到二维平面上,不过目前以我的知识面来看,二维线段树只能解决区间最值问题
(区间和什么的用二维树状数组就好了)
然而二维线段树有两种写法(二者差别还是蛮大的):
单点修改+区间最值查询
这种线段树的特点就是:先确定x再确定y
update(x)需要通过y的查找完成

int mx[N<<2][N<<2],mn[N<<2][N<<2];
int maxx,minn,a[N][N];

void update(int x,int y) {
    mn[x][y]=min(mn[x][y<<1],mn[x][y<<1|1]);
    mx[x][y]=max(mn[x][y<<1],mn[x][y<<1|1]);
}

void build_y(int bh,int l,int r,int x,int xrt) {
    if (l==r) {
        if (x!=-1) mn[xrt][bh]=mx[xrt][bh]=a[x][l];
        else {
            mx[xrt][bh]=max(mx[xrt<<1][bh],mx[xrt<<1|1][bh]);
            mn[xrt][bh]=min(mn[xrt<<1][bh],mn[xrt<<1|1][bh]);
        }
        return;
    }
    int mid=(l+r)>>1;
    build_y(bh<<1,l,mid,x,xrt);
    build_y(bh<<1|1,mid+1,r,x,xrt);
    update(xrt,bh);
}

void build_x(int bh,int l,int r) {
    if (l==r) {
        build_y(1,1,n,l,bh);
        return;
    }
    int mid=(l+r)>>1;
    build_x(bh<<1,l,mid);
    build_x(bh<<1,mid+1,r);

    build_y(1,1,n,-1,bh);            //update(bh)
}

void change_y(int bh,int l,int r,int x,int z,int xrt) {
    if (l==r) {
        if (z!=-1) mn[xrt][bh]=mx[xrt][bh]=z;
        else {
            mn[xrt][bh]=min(mn[xrt<<1][bh],mn[xrt<<1|1][bh]);
            mx[xrt][bh]=max(mx[xrt<<1][bh],mx[xrt<<1|1][bh]);
        }
        return;
    }
    int mid=(l+r)>>1;
    if (x<=mid) change_y(bh<<1,l,mid,x,z,xrt);
    else change_y(bh<<1|1,mid+1,r,x,z,xrt);
    update(xrt,bh);
}

void change_x(int bh,int l,int r,int x,int y,int z) {
    if (l==r) {
        change_y(1,1,n,y,z,bh);
        return;
    }
    int mid=(l+r)>>1;
    if (x<=mid) change_x(bh<<1,l,mid,x,y,z);
    else change_x(bh<<1,mid+1,r,x,y,z);

    change_y(1,1,n,y,-1,bh);
}

void ask_y(int bh,int l,int r,int L,int R,int xrt) {
    if (l>=L&&r<=R) {
        maxx=max(maxx,mx[xrt][bh]);
        minn=min(minn,mn[xrt][bh]);
        return;
    }
    int mid=(l+r)>>1;
    if (L<=mid) ask_y(bh<<1,l,mid,L,R,xrt);
    if (R>mid) ask_y(bh<<1|1,mid+1,r,L,R,xrt);
}

void ask_x(int bh,int l,int r,int xl,int xr,int yl,int yr) {
    if (l>=xl&&r<=xr) {
        ask_y(1,1,n,yl,yr,bh);
        return;
    }
    int mid=(l+r)>>1;
    if (xl<=mid) ask_x(bh<<1,l,mid,xl,xr,yl,yr);
    if (xr>mid) ask_x(bh<<1|1,mid+1,r,xl,xr,yl,yr);
}

区间修改+区间最值查询
二维树状数组本身就不好push和update,所以我们要标记永久化
记录两个标记,A精准覆盖,B标记永久化
在查询的时候,B是准确的

int xl,xr,yl,yr,n,m;

struct node_y{
    int a[N<<2],b[N<<2];

    void change(int bh,int l,int r,int L,int R,int z) {
        b[bh]=max(b[bh],z);
        if (l>=L&&r<=R) {
            a[bh]=max(a[bh],z);
            return;
        }
        int mid=(l+r)>>1;
        if (L<=mid) change(bh<<1,l,mid,L,R,z);
        if (R>mid) change(bh<<1|1,mid+1,r,L,R,z);
    }
    int ask(int bh,int l,int r,int L,int R) {
        if (l>=L&&r<=R) return b[bh];
        int mid=(l+r)>>1;
        int ans=a[bh];
        if (L<=mid) ans=max(ans,ask(bh<<1,l,mid,L,R));
        if (R>mid) ans=max(ans,ask(bh<<1|1,mid+1,r,L,R));
        return ans;
    }
};
struct node_x{
    node_y a[N<<2],b[N<<2];

    void change(int bh,int l,int r,int L,int R,int z) {
        b[bh].change(1,1,m,yl,yr,z);
        if (l>=L&&r<=R) {
            a[bh].change(1,1,m,yl,yr,z);
            return;
        }
        int mid=(l+r)>>1;
        if (L<=mid) change(bh<<1,l,mid,L,R,z);
        if (R>mid) change(bh<<1|1,mid+1,r,L,R,z);
    } 
    int ask(int bh,int l,int r,int L,int R) {
        if (l>=L&&r<=R) return b[bh].ask(1,1,m,yl,yr);
        int mid=(l+r)>>1;
        int ans=a[bh].ask(1,1,m,yl,yr);
        if (L<=mid) ans=max(ans,ask(bh<<1,l,mid,L,R));
        if (R>mid) ans=max(ans,ask(bh<<1|1,mid+1,r,L,R));
        return ans;
    }
}T;
树链剖分

树链剖分的精髓就在于处理树上路径(路径和,路径最大值)
本身可以说就是一个模板,但是结合其他算法的变化就比较多了

经典例题:(超哥)线段树+树链剖分
经典例题:维护带修改的带权重心到其余点的带权距离和

const int N=10010;
struct node{
    int y,nxt;
};
node way[N<<1];
struct Tree{
    ll sum,ad;
};
Tree t[N];
int n,m,st[N],tot=0,clo=0;
int pre[N],size[N],son[N],top[N],num[N],shu[N],deep[N],out[N];
ll p,a[N];

void add(int u,int w) {
    tot++;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
    tot++;way[tot].y=u;way[tot].nxt=st[w];st[w]=tot;
}

void dfs_1(int now,int fa,int dep) {
    pre[now]=fa;
    size[now]=1;
    deep[now]=dep;
    int maxx=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa) {
            dfs_1(way[i].y,now,dep+1);
            size[now]+=size[way[i].y];
            if (size[way[i].y]>maxx)
                maxx=size[way[i].y],son[now]=way[i].y;
        }
}

void dfs_2(int now,int fa) {
    if (son[fa]==now) top[now]=top[fa];
    else top[now]=now;
    num[now]=++clo; shu[clo]=now;
    if (son[now]) {
        dfs_2(son[now],now);
        for (int i=st[now];i;i=way[i].nxt)
            if (way[i].y!=fa&&way[i].y!=son[now])
                dfs_2(way[i].y,now);
    }
    out[now]=clo;
}

void push(int bh,int l,int r) {
    if (l==r) {
        t[bh].ad=0;      //清零 
        return;
    }
    if (!t[bh].ad) return;
    int lc=bh<<1,rc=bh<<1|1,mid=(l+r)>>1;

    t[lc].sum+=t[bh].ad*(mid-l+1); t[lc].sum%=p;
    t[lc].ad+=t[bh].ad; t[lc].ad%=p;
    t[rc].sum+=t[bh].ad*(r-mid); t[rc].sum%=p;
    t[rc].ad+=t[bh].ad; t[rc].ad%=p;
    t[bh].ad=0;
}

void update(int bh) {
    t[bh].sum=(t[bh<<1].sum+t[bh<<1|1].sum)%p;
}

void build(int bh,int l,int r) {
    t[bh].ad=t[bh].sum=0;
    if (l==r) {
        t[bh].sum=a[shu[l]]%p;
        return;
    }
    int mid=(l+r)>>1;
    build(bh<<1,l,mid);
    build(bh<<1|1,mid+1,r);
    update(bh);
}

void change(int bh,int l,int r,int L,int R,ll z) {
    push(bh,l,r);
    if (l>=L&&r<=R) {
        t[bh].sum+=(r-l+1)*z; t[bh].sum%=p;
        t[bh].ad+=z; t[bh].ad%=p;
        return;
    }
    int mid=(l+r)>>1;
    if (L<=mid) change(bh<<1,l,mid,L,R,z);
    if (R>mid) change(bh<<1|1,mid+1,r,L,R,z);
    update(bh);
}

ll ask(int bh,int l,int r,int L,int R) {
    push(bh,l,r);
    if (l>=L&&r<=R) return t[bh].sum%p;
    int mid=(l+r)>>1;
    ll ans=0;
    if (L<=mid) ans=(ans+ask(bh<<1,l,mid,L,R))%p;
    if (R>mid) ans=(ans+ask(bh<<1|1,mid+1,r,L,R))%p;
    return ans%p;
}

void changesum(int x,int y,ll z) {                //传入原树上的结点编号
    int f1=top[x];
    int f2=top[y];
    while (f1!=f2) {
        if (deep[f1]<deep[f2]) swap(x,y),swap(f1,f2);
        change(1,1,n,num[f1],num[x],z);
        x=pre[f1];
        f1=top[x];
    }
    if (num[x]>num[y]) swap(x,y);
    change(1,1,n,num[x],num[y],z);
}

ll asksum(int x,int y) {                       //传入原树上的结点编号 
    int f1=top[x];
    int f2=top[y];
    ll ans=0;
    while (f1!=f2) {
        if (deep[f1]<deep[f2]) swap(x,y),swap(f1,f2);
        ans=(ans+ask(1,1,n,num[f1],num[x]))%p;
        x=pre[f1];
        f1=top[x];
    }      
    if (num[x]>num[y]) swap(x,y);
    ans=(ans+ask(1,1,n,num[x],num[y]))%p;
    return ans;
}  
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值