【线段树模板以及Codeforces线段树专题总结】

线段树模板

//线段树一般用来解决可合并的区间问题
//树上维护子树区间的问题也可以通过dfs序+线段树去维护
//不要质疑线段树写法的正确性,首先检查做法是否正确
//不要忘记build,dfs序问题build时注意下标
//数组记得开4倍,push_down一定要想好顺序
//对于每个问题,我们只要考虑清楚push_down和push_up的写法即可
//当问题不好处理的时候,想象一下对于单次询问暴力的做法,再去想区间合并操作
//对于求一个区间内权值大于y的最小下标x时,就先看左区间是不是能找到这样的值,找不到再找右区间
//若找不到返回-1否则返回下标,用区间max<=y进行剪枝防止复杂度退化
//TLE有可能是数据溢出的问题,检查函数传参的变量类型(是否需要long long)
//TLE不要怀疑线段树的复杂度,要想想有没有优化或者哪里是不是死循环
//多个update或者query检查是否调用错误
//如果区间赋值对子区间的改变有多次的影响,要把add数组更改带来的变化存在另一个lazy数组
struct T
{
    int l,r,mid;
    int add,sum;
}tree[maxn<<2];
void push_up(int rt)
{
    //在这里合并子区间
}
void push_down(int rt)
{
    if(tree[rt].add)
    {
    	//在这里下传标记
        tree[rt].add=0;
    }
}
void build(int rt,int l,int r)
{
    tree[rt].add=0;
    tree[rt].l=l;
    tree[rt].r=r;
    if(l==r)
    {
        tree[rt].sum=a[l];
        return ;
    }
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    push_up(rt);
}
void update(int rt,int l,int r)
{
    if(tree[rt].r<l||tree[rt].l>r) return ;
    if(tree[rt].l>=l&&tree[rt].r<=r)
    {
        //更新区间答案,并更新lazy数组
        return ;
    }
    push_down(rt);
    if(tree[rt].mid>=l) update(rt<<1,l,r);
    if(tree[rt].mid<r) update(rt<<1|1,l,r);
    push_up(rt);
}
int query(int rt,int l,int r)
{
    if(tree[rt].r<l||tree[rt].l>r) return 0;
    if(tree[rt].l>=l&&tree[rt].r<=r) // 返回本区间答案。
    push_down(rt);
    int ans=0;
    if(tree[rt].mid>=l) ans+=query(rt<<1,l,r);
    if(tree[rt].mid<r) ans+=query(rt<<1|1,l,r);
    push_up(rt);
    return ans;
}

线段树第一题

链接:

Codeforces Round #442 (Div. 2)E. Danil and a Part-time Job

题意:

给你一颗有根树,树上每一个节点有一个灯,现在要支持两种操作,第一种操作是统计一颗子树内开着的灯个数。第二种操作是将一个子树内的所有灯状态改变(开灯->关灯,关灯->开灯)。

做法:

首先对于树的dfs序建立线段树,这样每一棵子树都能表示成一段区间,之后问题就转换为,区间改变01状态,区间求和。
这个问题我们可以通过lazy数组实现,首先我们统计出最初状态下每棵子树内灯的状况,将每棵子树内亮着的灯的个数表示为 s u m sum ,对于区间更新,我们只需要用一个 a d d add 表示这个区间是否被翻转, a d d add 每次和 1 1 进行异或,这表示如果偶数次翻转,区间就会回到最初的情况,之后我们只需要知道这个区间是否被翻转也就可以更新 s u m sum

对于 p u s h _ d o w n push \_ down 函数,我们首先要更新子区间的 a d d add 数组 ( 0 &gt; 1 , 1 &gt; 0 ) (0-&gt;1,1-&gt;0) ,之后更新子区间的 s u m sum ,这里我们要知道子区间内一共点的个数,也就是 ( r l + 1 ) (r-l+1) ,之后再更新 s u m sum 即可。

对于 p u s h _ u p push\_up 函数,只需要合并子区间的和即可。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn = 2e5+5;
int tim;
int t[maxn];
int sum[maxn],sum2[maxn];
int in[maxn],ou[maxn],dfn[maxn];
vector<int> G[maxn];
void dfs(int rt,int fa)
{
    in[rt]=++tim;
    dfn[tim]=rt;
    for(int i=0;i<G[rt].size();i++)
    {
        int to=G[rt][i];
        if(to==fa) continue;
        dfs(to,rt);
    }
    ou[rt]=tim;
}
struct T
{
    int l,r,mid;
    int add,sum;
}tree[maxn<<2];
void push_up(int rt)
{
    tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum;
}
void push_down(int rt)
{
    if(tree[rt].add)
    {
        tree[rt<<1].add^=1;
        tree[rt<<1].sum=(tree[rt<<1].r-tree[rt<<1].l+1)-tree[rt<<1].sum;
        tree[rt<<1|1].add^=1;
        tree[rt<<1|1].sum=(tree[rt<<1|1].r-tree[rt<<1|1].l+1)-tree[rt<<1|1].sum;
        tree[rt].add=0;
    }
}
void build(int rt,int l,int r)
{
    tree[rt].add=0;
    tree[rt].l=l;
    tree[rt].r=r;
    if(l==r)
    {
        tree[rt].sum=t[dfn[l]];//dfs序的问题这里记得dfn
        return ;
    }
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    push_up(rt);
}
void update(int rt,int l,int r)
{
    if(tree[rt].r<l||tree[rt].l>r) return ;
    if(tree[rt].l>=l&&tree[rt].r<=r)
    {
        tree[rt].sum=(tree[rt].r-tree[rt].l+1)-tree[rt].sum;
        tree[rt].add^=1;
        return ;
    }
    push_down(rt);
    if(tree[rt].mid>=l) update(rt<<1,l,r);
    if(tree[rt].mid<r) update(rt<<1|1,l,r);
    push_up(rt);
}
int query(int rt,int l,int r)
{
    if(tree[rt].r<l||tree[rt].l>r) return 0;
    if(tree[rt].l>=l&&tree[rt].r<=r) return tree[rt].sum;
    push_down(rt);
    int ans=0;
    if(tree[rt].mid>=l) ans+=query(rt<<1,l,r);
    if(tree[rt].mid<r) ans+=query(rt<<1|1,l,r);
    push_up(rt);
    return ans;
}
int main()
{
    int n,x;
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
    {
        scanf("%d",&x);
        G[x].push_back(i);
        G[i].push_back(x);
    }
    for(int i=1;i<=n;i++) scanf("%d",&t[i]);
    dfs(1,-1);//获取dfs序
    build(1,1,n);
    int q;
    scanf("%d",&q);
    while(q--)
    {
        char op[4];
        int v;
        scanf("%s%d",op,&v);
        if(op[0]=='p') update(1,in[v],ou[v]);
        else printf("%d\n",query(1,in[v],ou[v]));
    }
    return 0;
}



线段树第二题

链接

Codecraft-18 and Codeforces Round #458 (Div. 1 + Div. 2, combined)

题意

给你一个区间,要支持两种区间操作。
第一种操作是单点更新,第二种操作是询问某个区间是否可以去掉一个元素使这段区间的 g c d gcd x x 的倍数。

做法

首先先要支持区间 g c d gcd 和单点更新,这个用普通的线段树就可以维护。
之后对于每个操作 2 2 ,我们先查询当前区间的 g c d gcd 是否为 x x 的倍数,若当前 g c d gcd x x 的倍数,则随意去掉哪个元素即可。
否则查询左右区间的 g c d gcd ,如果两个子区间的 g c d gcd 都不是 x x 的倍数,则至少删除两个元素,所以直接返回 f a l s e false
如果其中一个是 x x 的倍数,则问题转换为一个子问题,只需要查询另一个子区间去掉一个元素 g c d gcd 能否是 x x 的倍数即可。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 5e5+5;
int a[maxn];
struct T
{
    int l,r,mid;
    int GCD;
}tree[maxn<<2];
void push_up(int rt)
{
    tree[rt].GCD=__gcd(tree[rt<<1].GCD,tree[rt<<1|1].GCD);
}
void build(int rt,int l,int r)
{
    tree[rt].l=l;
    tree[rt].r=r;
    if(l==r)
    {
        tree[rt].GCD=a[l];
        return ;
    }
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    push_up(rt);
}
void update(int rt,int pos,int val)
{
    if(tree[rt].l==tree[rt].r)
    {
        tree[rt].GCD=val;
        return ;
    }
    if(pos<=tree[rt].mid) update(rt<<1,pos,val);
    else update(rt<<1|1,pos,val);
    push_up(rt);
}
int query(int rt,int l,int r)
{
    if(tree[rt].r<l||tree[rt].l>r) return 0;
    if(tree[rt].l>=l&&tree[rt].r<=r)
    {
        return tree[rt].GCD;
    }
    int ans=0;
    if(tree[rt].mid>=l) ans=__gcd(ans,query(rt<<1,l,r));
    if(tree[rt].mid<r) ans=__gcd(ans,query(rt<<1|1,l,r));
    return ans;
}
int findpos(int rt,int l,int r,int x)
{
    if(l==r) return 1;//查询到长度为1的区间,说明操作合法
    int ansl=x,ansr=x;
    if(tree[rt].mid>=l) ansl=query(rt<<1,l,tree[rt].mid);
    if(tree[rt].mid<r) ansr=query(rt<<1|1,tree[rt].mid+1,r);
    ansl=__gcd(ansl,x);
    ansr=__gcd(ansr,x);
    if(ansl!=x&&ansr!=x) return -1;//两个子区间的gcd都不是x的倍数,一定不合法
    else if(ansl==x&&ansr==x) return 1;//两个子区间的gcd都是x的倍数,一定合法
    else if(ansl==x) return findpos(rt<<1|1,tree[rt].mid+1,r,x);//转换为子问题求解
    else return findpos(rt<<1,l,tree[rt].mid,x);
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build(1,1,n);
    int q;
    scanf("%d",&q);
    while(q--)
    {
        int op,l,r,x;
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d%d%d",&l,&r,&x);
            if(findpos(1,l,r,x)==1) puts("YES");
            else puts("NO");
        }
        else
        {
            scanf("%d%d",&l,&x);
            update(1,l,x);
        }
    }
    return 0;
}


线段树第三题

链接:

Codeforces Round #111 (Div. 2) E. Buses and People

题意

有n个公交车,每个公交车的信息有 s i s_i (公交车起始下标), f i f_i (公交车终点下标), t i t_i (公交车发车时间)。
有m个乘客,每个乘客的信息有 l i l_i (乘客起始下标), r i r_i (乘客下车的下标), b i b_i (乘客上车时间)。
乘客 i i 坐上公交车 j j 的前提是 s j l i s_j \leq l_i , r i f j r_i \leq f_j , b i t j b_i \leq t_j ,问每一个乘客能坐的公交车中发车时间最小的公交车编号。

做法

首先先将所有公交车和乘客放到一起,之后按照起始下标排序,这样就能保证每个人乘客之前的公交车一定满足 s j l i s_j \leq l_i

之后我们对时间进行离散化建立线段树,每个节点存储当前t能到达的最大的 f f ,对于每个乘客,我们只需要查找小于等于 t i t_i 的时间区间中最小的满足 r i f j r_i \leq f_j 的t即可。

所以问题就转换为给定一个区间,求区间内最小的下标满足权值大于某个给定值。这个问题只需要在线段树上优先查询左子区间是否有满足情况的点,向下递归询问即可,注意要用区间max进行剪枝。

代码


#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 2e5+5;
struct T
{
    int l,r,mid;
    int val,id;
}tree[maxn<<2];
void push_up(int rt)
{
    tree[rt].val=max(tree[rt<<1].val,tree[rt<<1|1].val);
}
void build(int rt ,int l,int r)
{
    tree[rt].l=l;
    tree[rt].r=r;
    if(l==r)
    {
        tree[rt].id=-1;
        tree[rt].val=0;
        return ;
    }
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    push_up(rt);
}
void update(int rt,int pos,int val,int id)
{
    if(tree[rt].l==tree[rt].r)
    {
        tree[rt].val=max(tree[rt].val,val);
        tree[rt].id=id;
        return ;
    }
    if(pos<=tree[rt].mid) update(rt<<1,pos,val,id);
    else update(rt<<1|1,pos,val,id);
    push_up(rt);
}
int query(int rt,int val,int l,int r)
{
    if(tree[rt].l>=l&&tree[rt].r<=r)
    {
        if(tree[rt].val<val) return -1;
    }
    if(tree[rt].l==tree[rt].r) return tree[rt].id;
    int ans=-1;
    if(tree[rt].mid>=l)
    {
        ans=query(rt<<1,val,l,r);
        if(ans!=-1) return ans;
    }
    if(tree[rt].id<r)
    {
        ans=query(rt<<1|1,val,l,r);
        if(ans!=-1) return ans;
    }
    return -1;
}
struct Bus
{
    int l,r,t,id;
    bool operator<(const Bus y)const
    {
        if(l==y.l) return id<y.id;//要注意一个l同时有公交车和乘客,先更新公交车信息。
        return l<y.l;
    }
}bus[maxn<<2];
int Hash[maxn<<2],cnt;
int ans[maxn<<2];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n+m;i++)
    {
        scanf("%d%d%d",&bus[i].l,&bus[i].r,&bus[i].t);
        bus[i].id=i;
        Hash[++cnt]=bus[i].t;
    }
    sort(bus+1,bus+1+n+m);
    sort(Hash+1,Hash+1+cnt);
    build(1,1,cnt);
    int d=unique(Hash+1,Hash+1+cnt)-Hash-1;//对时间进行离散化
    for(int i=1;i<=n+m;i++)
    {
        int pos=lower_bound(Hash+1,Hash+1+d,bus[i].t)-Hash;
        if(bus[i].id<=n) update(1,pos,bus[i].r,bus[i].id);//公交车信息进行更新
        else ans[bus[i].id-n]=query(1,bus[i].r,pos,cnt);//乘客进行查询
    }
    for(int i=1;i<=m;i++) printf("%d ",ans[i]);
    return 0;
}


线段树第四题

链接:

Codeforces Beta Round #19 D. Points

题意

给你一个笛卡尔坐标系,现在要支持三种操作,第一种操作是添加一个点(x,y),第二种操作是删除一个点(x,y), 第三种操作是查询严格在点(x,y)右上角的点中,横坐标最小的点,如果有多个点,选择纵坐标最小的那个。

做法

首先题中给出的坐标范围都是1e9,所以需要对x轴进行离散化,建立线段树,每个节点存储区间内所有点中y的最大值,所以叶子节点存储的是当前x下y的最大值。 之后对于每个x开一个set存储横坐标为x的所有y。
之后对于操作1,2,我们只需要单点更新,并且在set中进行insert和erase。
对于操作3,我们首先转换为经典问题,求x+1到inf区间内最小的下标满足权值大于y,我们只要在线段树上优先看左子树是否有满足条件的点,转换为自问题递归求解即可,注意要用区间max进行剪枝,不然复杂度会退化。
这样我们就知道最小的存在大于y的下标x,之后在x所在的set中upper_bound查找第一个大于y的值即可。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
const int maxn = 2e5+5;
struct T
{
    int l,r,mid;
    int val,id;
}tree[maxn<<2];
multiset<int> s[maxn<<2];
void push_up(int rt)
{
    tree[rt].val=max(tree[rt<<1].val,tree[rt<<1|1].val);
}
void build(int rt,int l,int r)
{
    tree[rt].l=l;
    tree[rt].r=r;
    tree[rt].val=0;
    if(l==r)
    {
        s[l].insert(-1);
        tree[rt].val=-1;
        return ;
    }
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    push_up(rt);
    return ;
}
void update(int rt,int pos,int val,int flag)
{
    if(tree[rt].l==tree[rt].r)//递归到叶子节点更新set
    {
        if(flag==0) s[tree[rt].l].erase(val);
        else s[tree[rt].l].insert(val);
        tree[rt].id=pos;
        tree[rt].val=*(s[tree[rt].l].rbegin());
        return ;
    }
    if(pos<=tree[rt].mid) update(rt<<1,pos,val,flag);
    else  update(rt<<1|1,pos,val,flag);
    push_up(rt);
    return ;
}
int query(int rt,int val,int l,int r)
{
    if(tree[rt].l>r||tree[rt].r<l) return -1;
    if(tree[rt].l>=l&&tree[rt].r<=r)//区间最大值进行剪枝
    {
        if(tree[rt].val<=val) return -1;
    }
    if(tree[rt].l==tree[rt].r) return tree[rt].id;
    if(tree[rt].mid>=l)//优先看左区间是否有满足情况的点
    {
        int res=query(rt<<1,val,l,r);
        if(res!=-1) return res;
    }
    if(tree[rt].mid<r)
    {
        int res=query(rt<<1|1,val,l,r);
        if(res!=-1) return res;
    }
    return -1;
}
struct data
{
    int flag,x,y;
}p[maxn];
char op[maxn];
int d;
int a[maxn];//原数组
int b[maxn];//每个位置离散后的值
int c[maxn];//表示离散后为i的原来的值为c[i]
int Hash[maxn];//hash去重数组
void GetHash(int a[],int n)
{
    for(int i=1;i<=n;i++) Hash[i]=a[i];
    sort(Hash+1,Hash+1+n);
    d=unique(Hash+1,Hash+1+n)-Hash-1;
    for(int i=1;i<=n;i++)
    {
        b[i]=lower_bound(Hash+1,Hash+1+d,a[i])-Hash;
        c[b[i]]=a[i];
    }
}
int main()
{
    int n,x,y;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s%d%d",	op,&x,&y);
        if(op[0]=='a') p[i].flag=1;
        else if(op[0]=='r') p[i].flag=2;
        else p[i].flag=3;
        p[i].x=x;
        p[i].y=y;
        a[i]=x;
    }
    GetHash(a,n);//对x轴进行离散化
    build(1,1,d);//对x轴建立线段树
    for(int i=1;i<=n;i++)
    {
        if(p[i].flag==1) update(1,b[i],p[i].y,1);
        else if(p[i].flag==2) update(1,b[i],p[i].y,0);
        else
        {
            int res=query(1,p[i].y,b[i]+1,d);
            if(res==-1) printf("-1\n");
            else
            {
                set<int>::iterator it = s[res].upper_bound(p[i].y);
                printf("%d %d\n",c[res],(*it));
            }
        }
    }
    return 0;
}


线段树第五题

链接:

CodeForces-483D Interesting Array

题意

让你构造一个数列,满足m种限制条件,每种限制条件是l,r,x,要求构造的序列区间[l,r] 与运算的值结果为x

做法

首先由一个拆位的做法,对于每次询问,拆成30位分别进行构造,对于每个为0的位,区间查询这一位下[l,r]的和是不是等于区间长度,也就是看区间[l,r]与的结果是否为0。对于每个为1的位,区间更新。但是这个做法是两个log的,楼主并有没卡过去。
之后楼主的做法是这样的,首先对于每个查询,我们可以看当前[l,r]区间与的结果,如果这个结果是x的子集(这里的子集是二进制下的01序列),那么说明我们可以通过区间或运算让区间[l,r]的结果变成x,否则一定不行会破坏之前的关系,于是只需要线段树维护区间与运算,区间更新或运算即可。(要注意同一个区间多次询问的情况特判一下)。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
typedef pair<int,int> pii;
const int maxn = 1e5+5;
struct T
{
    int l,r,mid;
    int sum,add;
}tree[maxn<<2];
void push_up(int rt)
{
    tree[rt].sum=tree[rt<<1].sum&tree[rt<<1|1].sum;
}
void push_down(int rt)
{
    if(tree[rt].add!=0)
    {
        int tmp=tree[rt].add;
        tree[rt<<1].add=tree[rt<<1].add|tmp;
        tree[rt<<1].sum=tree[rt<<1].sum|tmp;
        tree[rt<<1|1].add=tree[rt<<1|1].add|tmp;
        tree[rt<<1|1].sum=tree[rt<<1|1].sum|tmp;
        tree[rt].add=0;
    }
}
void build(int rt,int l,int r)
{
    tree[rt].l=l;
    tree[rt].r=r;
    tree[rt].sum=0;
    tree[rt].add=0;
    if(l==r) return ;
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    return ;
}
void update(int rt,int l,int r,int val)
{
    if(tree[rt].l>r||tree[rt].r<l) return ;
    if(tree[rt].sum==tree[rt].r-tree[rt].l+1) return ;
    if(tree[rt].l>=l&&tree[rt].r<=r)
    {
        tree[rt].sum=tree[rt].sum|val;
        tree[rt].add=tree[rt].add|val;
        return ;
    }
    push_down(rt);
    if(tree[rt].mid>=l) update(rt<<1,l,r,val);
    if(tree[rt].mid<r) update(rt<<1|1,l,r,val);
    push_up(rt);
    return ;
}
int query(int rt,int l,int r)
{
    if(tree[rt].l>r||tree[rt].r<l) return 0;
    if(tree[rt].l>=l&&tree[rt].r<=r) return tree[rt].sum;
    push_down(rt);
    int ans=(1<<30)-1;
    if(tree[rt].mid>=l) ans=ans&query(rt<<1,l,r);
    if(tree[rt].mid<r) ans=ans&query(rt<<1|1,l,r);
    push_up(rt);
    return ans;
}
int ans[maxn];
map<pii,int> mp;
int main()
{
    int n,m,l,r,q;
    scanf("%d%d",&n,&m);
    build(1,1,n);
    int flag=0;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&l,&r,&q);
        if(mp.count(pii(l,r)))
        {
            if(mp[pii(l,r)]!=q) flag=1;
        }
        mp[pii(l,r)]=q;
        if(flag==1) continue;
        int res=query(1,l,r);
        if((q&res)==res)
        {
            int tmp=q-res;
            update(1,l,r,tmp);
        }
        else flag=1;
    }
    if(flag==1) return 0*puts("NO");
    for(int i=1;i<=n;i++) ans[i]=query(1,i,i);
    puts("YES");
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
    return 0;
}


线段树第六题

链接:

Educational Codeforces Round 52 C. Make It Equal

题意:

给你一些从左到右摆放的n堆正方体,每堆正方体由一些正方体堆叠而成,现在每次可以沿着某个高度砍一刀,这个高度之上的正方体都会被砍掉,要求是掉落的正方体个数不超过k,问最少砍多少刀能让所有正方体高度相同。

做法:

首先可以桶排序,高度从高到低统计出每种高度正方体高度的个数,之后从高到低贪心的看是不是能砍即可。时间复杂度 O ( n log n ) O(n \log n)
但是楼主拉线段树专题不小心看到了这道题,于是给出线段树的做法。首先依旧是统计出每种高度的正方体的个数,之后我们统计前缀和,之后问题就转换为区间[1,200000]内查找小于k的最大值,找到之后区间更新即可。时间复杂度 O ( n log n ) O(n \log n) ,但是这个做法不太优雅,要注意很多细节。
两个做法都要注意不需要砍的情况。

代码:

做法一:

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 4e5+5;
ll a[maxn],b[maxn],sum[maxn];
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    if(a[1]==a[n])
    {
        puts("0");
        return 0;
    }
    int pos=1;
    for(int i=1;i<=n;i++)
    {
        while(pos<=200000&&pos<=a[i])
        {
            sum[pos]=(n-i+1);
            pos++;
        }
    }
    for(int i=1;i<=200000;i++) b[i]=sum[200000-i+1];
    pos=1;
    ll cnt=0;
    int ans=0;
    while(pos<=200000)
    {
        if(cnt+b[pos]<=k)
        {
            cnt+=b[pos];
            pos++;
            if(pos==200001) ans++;
        }
        else
        {
            ans++;
            if(b[pos]==n) break;
            cnt=b[pos];
            pos++;
        }
    }
    printf("%d\n",ans);
    return 0;
}

做法二:

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int maxn = 4e5+5;
struct T
{
    int l,r,mid;
    ll add,val;
}tree[maxn<<2];
ll a[maxn];
ll sum[maxn];
ll b[maxn];
void up(int rt)
{
    tree[rt].val=min(tree[rt<<1].val,tree[rt<<1|1].val);
    return;
}
void down(int rt)
{
    if(tree[rt].add!=0)
    {
        ll tmp=tree[rt].add;
        tree[rt<<1].val+=tmp;
        tree[rt<<1|1].val+=tmp;
        tree[rt<<1].add+=tmp;
        tree[rt<<1|1].add+=tmp;
        tree[rt].add=0;
    }
}
void build(int rt,int l,int r)
{
    tree[rt].l=l;
    tree[rt].r=r;
    tree[rt].add=0;
    if(l==r)
    {
        tree[rt].val=b[l];
        return ;
    }
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    up(rt);
}
pll query(int rt,int l,int r,ll val)
{
    if(tree[rt].val>val) return pll(-1,-1);
    if(tree[rt].l==tree[rt].r) return pll(tree[rt].l,tree[rt].val);
    down(rt);
    if(tree[rt<<1|1].val<=val) return query(rt<<1|1,l,r,val);
    else return query(rt<<1,l,r,val);
}
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    if(a[1]==a[n])
    {
        puts("0");
        return 0;
    }
    int pos=1;
    for(int i=1;i<=n;i++)
    {
        while(pos<=200000&&pos<=a[i])
        {
            sum[pos]=(n-i+1);
            pos++;
        }
    }
    for(int i=1;i<=200000;i++) b[i]=sum[200000-i+1];
    for(int i=1;i<=200000;i++) b[i]=b[i-1]+b[i];
    build(1,1,200000);
    pll tt=pll(0,0);
    int ans=0;
    ll cc=0;
    while(true)
    {
        tt=query(1,1,200000,k);
        ans++;
        tree[1].val-=tt.second;
        tree[1].add-=tt.second;
        if(tt.first==200000) break;
        cc+=tt.second;
        ll tm=b[tt.first+1]-cc;
        if(tm==n) break;
    }
    printf("%d\n",ans);
    return 0;
}


线段树第七题

链接:

http://codeforces.com/problemset/problem/296/C

题意:

给你n个数,有m次操作,每次操作为区间加,现在有k次大操作,每次大操作执行m个操作的[l,r]区间,问k次大操作之后这n个数变成什么样?

做法:

第一种做法就是先对k次大操作进行差分,就知道每个小操作执行次数,之后线段树维护区间加即可。
第二种做法就是知道每个小操作执行次数之后,在原数组上再差分一次,就可以得到每个数加多少值, O ( n ) O(n) 即可完成。

代码:

做法一:

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
struct T
{
    int l,r,mid;
    ll val,add;
}tree[maxn<<2];
int a[maxn];
void up(int rt)
{
    tree[rt].val=tree[rt<<1].val+tree[rt<<1|1].val;
}
void down(int rt)
{
    if(tree[rt].add)
    {
        ll tmp=tree[rt].add;
        tree[rt<<1].add+=tmp;
        tree[rt<<1].val+=1LL*(tree[rt<<1].r-tree[rt<<1].l+1)*tmp;
        tree[rt<<1|1].add+=tmp;
        tree[rt<<1|1].val+=1LL*(tree[rt<<1|1].r-tree[rt<<1|1].l+1)*tmp;
        tree[rt].add=0;
    }
}
void build(int rt,int l,int r)
{
    tree[rt].l=l;
    tree[rt].r=r;
    tree[rt].add=0;
    if(l==r)
    {
        tree[rt].val=a[l];
        return ;
    }
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    up(rt);
}
void update(int rt,int l,int r,ll val)
{
    if(tree[rt].l>r||tree[rt].r<l) return ;
    if(tree[rt].l>=l&&tree[rt].r<=r)
    {
        tree[rt].add+=val;
        tree[rt].val+=1LL*(tree[rt].r-tree[rt].l+1)*val;
        return ;
    }
    down(rt);
    if(tree[rt].mid>=l) update(rt<<1,l,r,val);
    if(tree[rt].mid<r) update(rt<<1|1,l,r,val);
    up(rt);
    return ;
}
ll query(int rt,int pos)
{
    if(tree[rt].l==tree[rt].r) return tree[rt].val;
    down(rt);
    if(pos<=tree[rt].mid) return query(rt<<1,pos);
    else return query(rt<<1|1,pos);
}
int l[maxn],r[maxn],val[maxn],cnt[maxn];
int tt[maxn];
int main()
{
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build(1,1,n);
    for(int i=1;i<=m;i++) scanf("%d%d%d",&l[i],&r[i],&val[i]);
    for(int i=1;i<=k;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        tt[u]++;
        tt[v+1]--;
    }
    int tmp=0;
    for(int i=1;i<=m;i++)
    {
        tmp+=tt[i];
        cnt[i]=tmp;
    }
    for(int i=1;i<=m;i++) update(1,l[i],r[i],1LL*cnt[i]*val[i]);
    for(int i=1;i<=n;i++) printf("%lld ",query(1,i));
    return 0;
}


做法二:

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
int a[maxn];
int l[maxn],r[maxn],val[maxn],cnt[maxn];
int tt[maxn];
ll sum[maxn];
int main()
{
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=m;i++) scanf("%d%d%d",&l[i],&r[i],&val[i]);
    for(int i=1;i<=k;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        tt[u]++;
        tt[v+1]--;
    }
    int tmp=0;
    for(int i=1;i<=m;i++)
    {
        tmp+=tt[i];
        cnt[i]=tmp;
    }
    ll tp=0;
    for(int i=1;i<=m;i++)
    {
        sum[l[i]]+=1LL*cnt[i]*val[i];
        sum[r[i]+1]-=1LL*cnt[i]*val[i];
    }
    for(int i=1;i<=n;i++)
    {
        tp+=sum[i];
        printf("%lld ",a[i]+tp);
    }
    return 0;
}


线段树第八题:

链接:

http://codeforces.com/contest/444/problem/C

题意:

给你n个元素,第i个元素最初的颜色是i,最初每个元素的权值为0,有两种操作,第一种操作是区间赋值x,如果之前元素的颜色为y,那么赋值之后他的权值增加abs(x-y),第二种操作是查询所有元素的权值和。

做法:

首先可以想象如果每次操作区间很大,那么所有元素趋向颜色相同,如果操作区间很小,则可以暴力更新,那么我们可以维护区间颜色是否相同,如果相同则用lazy进行更新,否则再去子区间进行更新,这里要注意lazy对于子区间的更新每次都有一个权值的增加,这个也要用一个lazy数组维护一下。

代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
struct T
{
    int l,r,mid;
    int col,add;
    ll sum,inc;
}tree[maxn<<2];
int col[maxn];
void up(int rt)
{
    if(tree[rt<<1].col==tree[rt<<1|1].col) tree[rt].col=tree[rt<<1].col;
    else tree[rt].col=0;

    tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum;
}
int maxx=0;
void down(int rt)
{
    if(tree[rt].add!=0)
    {
        int tmp=tree[rt].add;
        tree[rt<<1].col=tmp;
        tree[rt<<1].add=tmp;

        tree[rt<<1|1].col=tmp;
        tree[rt<<1|1].add=tmp;
        tree[rt].add=0;
    }

    if(tree[rt].inc!=0)
    {
        ll tmp=tree[rt].inc;
        tree[rt<<1].sum+=1LL*tmp*(tree[rt<<1].r-tree[rt<<1].l+1);
        tree[rt<<1].inc+=tmp;
        tree[rt<<1|1].sum+=1LL*tmp*(tree[rt<<1|1].r-tree[rt<<1|1].l+1);
        tree[rt<<1|1].inc+=tmp;

        tree[rt].inc=0;
    }

}
void build(int rt,int l,int r)
{
    tree[rt].l=l;
    tree[rt].r=r;
    tree[rt].add=0;
    tree[rt].inc=0;
    if(l==r)
    {
        tree[rt].col=col[l];
        tree[rt].sum=0;
        return ;
    }
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    up(rt);
    return ;
}
void update(int rt,int l,int r,int co)
{
    if(tree[rt].l>r||tree[rt].r<l) return ;
    if(tree[rt].l>=l&&tree[rt].r<=r&&tree[rt].col!=0)
    {
        tree[rt].sum=tree[rt].sum+1LL*abs(co-tree[rt].col)*(tree[rt].r-tree[rt].l+1);
        tree[rt].inc+=abs(co-tree[rt].col);
        tree[rt].col=co;
        tree[rt].add=co;
        return ;
    }
    down(rt);
    if(tree[rt].mid>=l) update(rt<<1,l,r,co);
    if(tree[rt].mid<r) update(rt<<1|1,l,r,co);
    up(rt);
}
ll query(int rt,int l,int r)
{
    if(tree[rt].l>r||tree[rt].r<l) return 0;
    if(tree[rt].l>=l&&tree[rt].r<=r) return tree[rt].sum;
    down(rt);
    ll ans=0;
    if(tree[rt].mid>=l) ans=ans+query(rt<<1,l,r);
    if(tree[rt].mid<r) ans=ans+query(rt<<1|1,l,r);
    return ans;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) col[i]=i;
    build(1,1,n);
    int cnt=0;
    while(m--)
    {
        int op,x,y,z;
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d%d%d",&x,&y,&z);
            update(1,x,y,z);
        }
        else
        {
            scanf("%d%d",&x,&y);
            printf("%lld\n",query(1,x,y));
        }
    }
    return 0;
}



线段树第九题:

链接:

https://codeforces.com/contest/707/problem/D

题意

有n个书架,每个书架上有m个位置可以放书,现在要维护4种操作。
1,x,y:如果第x个书架的第y个位置没有书,在第x个书架的第y个位置放置一本书
2,x,y:如果第x个书架的第y个位置有书,把第x个书架的第y个位置的书拿走
3,x: 将第x书架所有有书的位置的书拿走,将x书架所有没有书的位置放上书
4,x:返回第x次操作后状态
要求每步操作之后返回当前书的个数。

做法

首先每一步操作都是从之前的某步操作转过来的,所以整个操作顺序是一颗有向树,我们只要按顺序dfs,问题就变成单点更新,区间求和,但是要注意的是,回溯撤销操作的时候要看操作的时候是否对状态进行改变,比如本来有书又放一本书,没有对状态进行改变,回溯操作的时候就不能取走这本书。

代码


#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 1e6+5;
struct data
{
    int op;
    int x,y;
}Q[maxn];
int n,m,q;
struct T
{
    int l,r,mid;
    int sum,add;
}tree[maxn<<2];
vector<int>G[maxn];
void down(int rt)
{
    if(tree[rt].add)
    {
        tree[rt<<1].sum=(tree[rt<<1].r-tree[rt<<1].l+1)-tree[rt<<1].sum;
        tree[rt<<1|1].sum=(tree[rt<<1|1].r-tree[rt<<1|1].l+1)-tree[rt<<1|1].sum;
        tree[rt<<1].add=1-tree[rt<<1].add;
        tree[rt<<1|1].add=1-tree[rt<<1|1].add;
        tree[rt].add=0;
    }
}
void up(int rt)
{
    tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum;
}
void build(int rt,int l,int r)
{
    tree[rt].l=l;
    tree[rt].r=r;
    tree[rt].sum=0;
    tree[rt].add=0;
    if(l==r) return;
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
}
bool update(int rt,int pos,int val)
{
    if(tree[rt].l==tree[rt].r)
    {
        int tt=tree[rt].sum;
        tree[rt].sum=val;
        return (tt!=val);
    }
    down(rt);
    bool ans;
    if(tree[rt].mid>=pos) ans=update(rt<<1,pos,val);
    else ans=update(rt<<1|1,pos,val);
    up(rt);
    return ans;
}
void update_(int rt,int l,int r)
{
    if(tree[rt].l>r||tree[rt].r<l) return ;
    if(tree[rt].l>=l&&tree[rt].r<=r)
    {
        tree[rt].add=1-tree[rt].add;
        tree[rt].sum=tree[rt].r-tree[rt].l+1-tree[rt].sum;
        return ;
    }
    down(rt);
    if(tree[rt].mid>=l) update_(rt<<1,l,r);
    if(tree[rt].mid<r) update_(rt<<1|1,l,r);
    up(rt);
}
int ans[maxn];
void dfs(int rt)
{
    int flag=0;
    if(rt!=0)
    {
        if(Q[rt].op==1)
        {
            if(update(1,(Q[rt].x-1)*m+Q[rt].y,1)) flag=1;
        }
        else if(Q[rt].op==2)
        {
            if(update(1,(Q[rt].x-1)*m+Q[rt].y,0)) flag=1;
        }
        else if(Q[rt].op==3) update_(1,(Q[rt].x-1)*m+1,Q[rt].x*m);
    }
    ans[rt]=tree[1].sum;
    for(int i=0;i<G[rt].size();i++)
    {
        dfs(G[rt][i]);
    }
    if(rt!=0)
    {
        if(Q[rt].op==1&&flag==1) update(1,(Q[rt].x-1)*m+Q[rt].y,0);
        else if(Q[rt].op==2&&flag==1) update(1,(Q[rt].x-1)*m+Q[rt].y,1);
        else if(Q[rt].op==3) update_(1,(Q[rt].x-1)*m+1,Q[rt].x*m);
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=q;i++)
    {
        scanf("%d",&Q[i].op);
        if(Q[i].op==1||Q[i].op==2)
        {
            G[i-1].push_back(i);
            scanf("%d%d",&Q[i].x,&Q[i].y);
        }
        else if(Q[i].op==3)
        {
            G[i-1].push_back(i);
            scanf("%d",&Q[i].x);
        }
        else
        {
            scanf("%d",&Q[i].x);
            G[Q[i].x].push_back(i);
        }
    }
    build(1,1,n*m);
    dfs(0);
    for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
    return 0;
}

发布了299 篇原创文章 · 获赞 117 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_38891827/article/details/89139958
今日推荐