4.11-4.17 周记线段树kuangbin 8-17 区间修改/区间根号/最大连续子区间/DFS序

线段树

count colors
特别的地方:“小线段”和区间端点的区别,和离散化maylor’s poster有点像,在操作的时候要缩小区间,注意题目给我们的是染色区间的端点,就是如果对[3,9]进行操作 ,只能是对[3,8]操作或者是对[4,9]操作,我选第一种,我用左一个单位的左端点储存这个单位的颜色,比如[3,9]段的颜色变成1,那就是以3为左端点单位段变成1,tr[3].lz=1,tr[4].lz=1…一直到tr[8].lz=1,注意,因为9已经是右边端点了,9储存的是[9,10]的颜色,所以tr[9].lz!=1。所以在写代码就要把右区间都减少1.

#include<cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=8000+7;
struct node{
    int l,r,lz;///这个时候lz和val的值完全等同 不需要再添加新的变量
   // int val; 
}tr[N<<2];
int vis[N],n,val[N],pre=-1;//因为区间从0开始 所以应该置为-1
void push_down(int k){
    if(tr[k].lz!=-1)///以后在这里完成判断!
        tr[k<<1].lz=tr[k<<1|1].lz=tr[k].lz;
    tr[k].lz=-1;//记得置为-1,否则在query下传的时候在更早时候的值会下传把后来的值覆盖掉
}
void build(int k,int l,int r){
    tr[k].l=l,tr[k].r=r;tr[k].lz = -1;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
}
void update(int k,int l,int r,int num){
    if(tr[k].l>=l&&tr[k].r<=r){
        tr[k].lz=num;
        return;
    }
    if(tr[k].lz!=-1)push_down(k);
    if(tr[k<<1].r>=l)update(k<<1,l,r,num);///不需要push_up的原因是 如果更新到这里 那么第一个if是不满足的 也就是当前的k并不是(l,r)区间的子集 所以这时候去push_up是错误的
    if(tr[k<<1|1].l<=r)update(k<<1|1,l,r,num);

}
void query(int k,int l,int r){
    if(tr[k].l==tr[k].r){
        if(tr[k].lz!=-1&&tr[k].lz!=pre)vis[tr[k].lz]++;///pre用来表示之前查找到的颜色 因为一段只能算一次
        // 一开始理解错题意以为是一块就加1 其实是一段就加1 如果上一次的那一段的颜色和当前相同那么它们是同一段
        pre=tr[k].lz;
        return;
    }
    push_down(k);
    int mid=(tr[k].l+tr[k].r)>>1;
    if(l<=mid) query(k<<1,l,r);
    if(r>mid)query(k<<1|1,l,r);
}
int main(){
    while(scanf("%d",&n)==1){
        pre=-1;
        memset(vis,0,sizeof vis);
        build(1,0,7999);
        for(int i=1;i<=n;i++){
            int l,r;
            scanf("%d%d%d",&l,&r,&val[i]);
            update(1,l,r-1,val[i]);
        }
        query(1,0,7999);///因为是“块”,所以需要缩小区间 否则会覆盖
        for(int i=0;i<=8000;i++){
            if(vis[i])printf("%d %d\n",i,vis[i]);
        }
        printf("\n");
    }
}

区间根号
很厉害的就是最后所有值变成1的时候就不用继续操作了,其实不是特别难想,当你没有什么思路就直接模拟一下题目的操作,总会到最后一步的。可以用区间和来判断最后到达1 ,因为都不会变成0,线段树真的好强啊!

#include<cstdio>
#include <algorithm>
#include<cmath>
#include<iostream>
#define ll long long
using namespace std;
const int N=1E5+7;
struct node{
    int l,r;
    ll sum;
}tr[N<<2];
int n;
void push_up(int k){tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;}
void build(int k,int l ,int r){
    tr[k].l=l,tr[k].r=r;
    if(l==r){
        ll tmp;
        scanf("%lld",&tmp);
        tr[k].sum=tmp;
        return;
    }
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    push_up(k);
}
void update(int k,int l,int r){
    if(tr[k].sum==(tr[k].r-tr[k].l+1))//判断最后变成1
       return ;
    if(tr[k].l==tr[k].r){
        tr[k].sum=floor(sqrt(tr[k].sum));
        return;
    }
    if(tr[k<<1].r>=l)update(k<<1,l,r);
    if(tr[k<<1|1].l<=r)update(k<<1|1,l,r);
    push_up(k);
}
ll query(int k,int l,int r){
    if(tr[k].l>=l&&tr[k].r<=r){
        return tr[k].sum;
    }ll ans=0;
    if(tr[k<<1].r>=l)ans+=query(k<<1,l,r);
    if(tr[k<<1|1].l<=r)ans+=query(k<<1|1,l,r);
    return ans;
}
int main(){
    int cnt=0,m;
    while(~scanf("%d",&n)){
        printf("Case #%d:\n",++cnt);
        build(1,1,n);

        scanf("%d",&m);
        for(int i=1;i<=m;i++){
            int op,l,r;
            scanf("%d%d%d",&op,&l,&r);
            if(l>r)swap(l,r);//被恶心到了但不是第一次,最好看清题意,不要主观啊!
            if(op==0){
                update(1,l,r); 
            }
            else {
                printf("%lld\n",query(1,l,r));
            }
        }
        printf("\n");
    }
    return 0;
}

维护最大最小值
直接区间查询

#include<cstdio>
#include<algorithm>
using namespace std;
/*
 * 首先是构建一棵树 其次是要维护的是区间的最大数和最小数
 *  最后是QUERY求出最大和最小的差
 * 看我乱糟糟的代码就知道根本不理解线段树,push_down是区间修改才需要用到,
 * 而且总区间也没有看清楚,以为n就是范围了。以后看题以为看懂了再看一遍,
 * 把每一个变量的含义弄清楚。
 * */
const int N=50007;
struct node{
    int maxx,minn,l,r,lzx,lzn;
}tr[N<<2];
int n,m,a[N],ma=0,mi=0x3f3f3f3f;
int ans;
/*void push_down(int k){
    tr[k<<1].lzn=tr[k<<1|1].lzn=tr[k].lzn;
    tr[k<<1].lzx=tr[k<<1|1].lzx=tr[k].lzx;
    tr[k<<1].maxx=tr[k<<1|1].maxx=tr[k].maxx;
    tr[k<<1].minn=tr[k<<1|1].maxx=tr[k].maxx;
    tr[k].lzn=tr[k].lzx=0;
}*/
void push_up(int k){
    tr[k].maxx=max(tr[k<<1].maxx,tr[k<<1|1].maxx);
    tr[k].minn=min(tr[k<<1].minn,tr[k<<1|1].minn);
}
///根本不用下传标记 下传标记是在区间修改的时候 但是现在你已经直接获得了
/// 从树的顶端走到最下端要O(m*n*logn),现在的赋初值需要O(n*logn)
void build(int k,int l,int r){
    tr[k].l=l,tr[k].r=r;
    if(l==r) {
        int tmp;
        scanf("%d",&tmp);
        tr[k].minn=tr[k].maxx=tmp;
        return; }
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    push_up(k);
}

/*void update(int k,int l,int r,int num){
    if(tr[k].l>=l&&tr[k].r<=r){
        tr[k].maxx=max(tr[k].maxx,num);
        tr[k].minn=min(tr[k].minn,num);
        *//*tr[k].lzn=tr[k].minn;
        tr[k].lzx=tr[k].maxx;*//*
        return;
    }
*//*    push_down(k);*//*
    update(k<<1,l,r,num);
    if(tr[k<<1|1].l<=r)update(k<<1|1,l,r,num);
    push_up(k);
}*/
void  query(int k,int l,int r){
    if(tr[k].l>=l&&tr[k].r<=r){
        ma=max(ma,tr[k].maxx);
        mi=min(mi,tr[k].minn);
        return ;
    }
    if(tr[k<<1].r>=l)query(k<<1,l,r);
    if(tr[k<<1|1].l<=r)query(k<<1|1,l,r);
}
int main(){
    scanf("%d%d",&n,&m);
    build(1,1,n);
    for(int i=1;i<=m;i++){
        int l,r;
        ans=0;
        scanf("%d%d",&l,&r);
        ma=-1,mi=0x3f3f3f3f;
        query(1,l,r);
        ///QUERY之后直接获取当前的最大值和最小值即可
        // printf("%d\n",ans);
        printf("%d\n",ma-mi);
    }return 0;
}

最大连续子区间

#include<iostream>
#include<cstdio>
#include<cstring>
#include <algorithm>
#include<string>
using namespace std;
#define in(n) scanf("%d",&n);
#define ch(n) scanf("\n%c",&n);///我不会说因为这个错误”\n “没有加我把这段代码打了四次的
#define re(i,n) for(int i=1;i<=n;i++)
const int N=50010;
struct TREE{
    int l,r,ll,rr,sum;
}tr[N<<2];
int n,m,de[N];
void build(int k,int l,int r){
    tr[k].l=l,tr[k].r=r,tr[k].ll=tr[k].rr=tr[k].sum=(r-l+1);//ll表示当前节点从左端点开始向右的最大连续子区间,rr同理 sum在下面的操作不会更新 只是用来判断当前节点是否为连续和用来更新ll和rr的值
    ///一开始打错成r+l-1,结果当然比预想的大很多 
    ///所以我应该思考哪里变大 从所有值更改的情况查找
    if(l==r)return;
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
   // cout<<"build"<<k<<l<<r<<' '<<tr[k].ll<<' '<<tr[k].rr<<endl;
}
void update(int k,int pos,int num){

    if(tr[k].l==tr[k].r){tr[k].ll=tr[k].rr=num;return;}
    int mid=(tr[k].l+tr[k].r)>>1;
    if(pos<=mid)update(k<<1,pos,num);
    else update(k<<1|1,pos,num);
    //这几个选择结构体现了线段树两个节点之间的区间连续这个特点
    //如果当前节点总的连续区间==当前节点左孩子节点从左端点向右的最大连续区间+当前节点右孩子节点从右端点向左的最大连续区间 说明当前节点是连续的
    // 注意一个节点(k)分下来的两个节点(k<<1),(k<<1|1)的区间和(先不管是否连续)一定是(k)节点的总区间 并且分下来的两个节点的区间不互相重叠
    if(tr[k].sum==tr[k<<1].ll+tr[k<<1|1].rr)tr[k].ll=tr[k].rr=tr[k].sum;
    else{
        if(tr[k<<1].ll==tr[k<<1].sum)tr[k].ll=tr[k<<1].sum+tr[k<<1|1].ll;
        else tr[k].ll=tr[k<<1].ll;
        if(tr[k<<1|1].rr==tr[k<<1|1].sum)tr[k].rr=tr[k<<1|1].sum+tr[k<<1].rr;
        else tr[k].rr=tr[k<<1|1].rr;
    }//cout<<"in"<<k<<endl<<tr[k].ll<<endl;
}
int query(int k,int pos){
    if(k==1){
        if(tr[k].l+tr[k].ll-1>=pos)return tr[k].ll;
        if(tr[k].r-tr[k].rr+1<=pos)return tr[k].rr;
    }
    if(tr[k].l+tr[k].ll-1>=pos)return tr[k].ll+tr[k-1].rr;//如果出现[k-1]是在当前节点的上一层,那么当前节点是整棵树的左端点,如果它存在 就一定会被(k==1)的情况覆盖掉
    //在上一个判断就已经return了 所以[k-1]只会和[k]同层  如果然后为什么加上左边或者右边就足够了呢?假设除了这两个节点还有其他节点的区间和它们相连,那么它们肯定拥有共同的父亲节点
    //那么它们在父亲节点就已经被更新完毕了 所以不会查询到当前这一层
    if(tr[k].r-tr[k].rr+1<=pos)return tr[k].rr+tr[k+1].ll;
    if(tr[k].l==tr[k].r)return 0;
    int mid=(tr[k].l+tr[k].r)>>1;
    if(pos<=mid)return query(k<<1,pos);
    else return query(k<<1|1,pos);
}
int main(){
    while(~scanf("%d%d",&n,&m)){
        memset(tr,0,sizeof tr);
        memset(de,0,sizeof de);
        build(1,1,n);
        int cnt=0;
        re(i,m){
            char op[9];
            cin>>op;
            if(op[0]=='R'&&cnt){
                update(1,de[cnt--],1);
            }
            else {
                int x;in(x)///其实在这里调试的时候print了两次 绝对是进入了两次循环 所以这个时候判断一下进入条件就可以了!
                ///学会一步步推理和窄化问题
                if(op[0]=='D'){
                    de[++cnt]=x;
                    update(1,x,0);
                }
                else printf("%d\n",query(1,x));
            }
           // for(int j=1;j<=n*4;j++)printf("%d %d %d %d %d\n",j,tr[j].l,tr[j].r,tr[j].ll,tr[j].rr);printf("\n");
        }
    }
    return 0;
}

dfs序
从祖先开始dfs,给每一个人一个开始和结束的时间戳,标记他从开始进入dfs的序号和跳出dfs的序号。
在这里插入图片描述

#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#define ll long long
using namespace std;
const int N=5E4+7;
vector<int>to[N];
int n,m,cnt,st[N],ed[N],vis[N];
struct node{
    int l,r;
    ll task,lz;
}tr[N<<2];
void init(){
    memset(tr,0,sizeof tr);
    memset(st,0,sizeof st);
    memset(ed,0,sizeof ed);
    memset(to,0,sizeof to);
    for(int i=1;i<=n;i++)to[i].clear();
    memset(vis,0,sizeof vis);
    cnt=0;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        to[v].push_back(u);
        vis[u]++;
    }
}
void dfs(int i){
    st[i]=++cnt;
    for(int j=0;j<to[i].size();j++)dfs(to[i][j]);
    ed[i]=cnt;
}
void build(int k,int l,int r){
    tr[k].l=l,tr[k].r=r,tr[k].task=tr[k].lz=-1;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
}
void push_down(int k){
    if(tr[k].lz==-1)return;
    tr[k<<1].task=tr[k<<1|1].task=tr[k<<1].lz=tr[k<<1|1].lz=tr[k].lz;
    tr[k].lz=-1;
}
void update(int k,int l ,int r,ll task){
    if(l<=tr[k].l&&tr[k].r<=r){
        tr[k].lz=tr[k].task=task;
        return;
    }push_down(k);
    int mid=(tr[k].l+tr[k].r)>>1;//cout<<"update"<<k<<' '<<l<<' '<<r<<' '<<tr[k].l<<' '<<tr[k].r<<' '<<tr[k].task<<endl;
    if(l<=mid)update(k<<1,l,r,task);
    if(r>mid)update(k<<1|1,l,r,task);
}
ll query(int k,int l,int r){
    if(l<=tr[k].l&&tr[k].r<=r)return tr[k].task;
    push_down(k);
    int mid=(tr[k].l+tr[k].r)>>1;//cout<<"query"<<k<<' '<<l<<' '<<r<<' '<<tr[k].l<<' '<<tr[k].r<<' '<<tr[k].task<<endl;
    if(l<=mid)return query(k<<1,l,r);
    if(r>mid)return query(k<<1|1,l,r);
}
int main(){
    int t;
    int num=0;
    cin>>t;
    while(t--){
        printf("Case #%d:\n",++num);
        scanf("%d",&n);
        init();
        build(1,1,n);
        for(int i=1;i<=n;i++){
            if(!vis[i]){
                dfs(i);
                break;
            }
        }
        for(int i=1;i<=n;i++)//cout<<st[i]<<' '<<ed[i]<<endl;
        scanf("%d",&m);
        while(m--){
            char op[10];
            int peo;cin>>op; scanf("%d",&peo);
            if(op[0]=='C'){
                printf("%lld\n",query(1,st[peo],ed[peo]));
            }
            else if(op[0]=='T'){
               //cout<<peo<<st[peo]<<ed[peo]<<"dd"<<endl;
                ll task;scanf("%lld",&task);
                update(1,st[peo],ed[peo],task);
            }
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值