线段树加强篇

本文探讨了如何利用数据结构如线段树和队列解决编程问题,包括查找第L小的数、KPI问题、Atlantis覆盖面积计算等。文章详细解释了lower_bound、erase和unique等函数的用法,并提供了优化后的代码实现,展示了如何在实际问题中提升算法效率。
摘要由CSDN通过智能技术生成


详情链接

A—Data Structure?

在这里插入图片描述

本题找第L小的数,就是找第几个1;
找到后将这个数删除,就是将这个1变成0;
向上更新,两项的和。第n个数就是n(n个1的和)

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
#define ll long long
const ll maxn = 1e7 + 5;
ll sum,n,m;
ll b[maxn*4], a[maxn],ans[maxn];
void push_up(ll rt)
 {
    b[rt] = b[rt << 1] + b[rt << 1 | 1] ;
}

void Build(ll l,ll r,ll rt)//创建树
{
    //如果达到叶节点
    if(l == r)
    {
        b[rt]=1;
        return ;
    }
    ll m=(l+r)>>1;
    //左右递归;

    Build(l,m,rt<<1);
    Build(m+1,r,rt<<1|1);
    push_up(rt);
}
void Update(ll L, ll l, ll r, ll rt)
{
    if(l == r)//找到第L点;
    {
       b[rt]=0;
       sum += l;
       return ;
    }
    int mid = l + r >> 1;
    if(L <= b[rt<<1])//该数是几就应该在第几位,前n项和如果小于l那就说明l不在这区间内
        Update(L, l, mid, rt << 1);
    else
        Update(L-b[rt<<1], mid + 1, r, rt << 1 | 1);
        push_up(rt);
}

int main()
{
    ll t,x,cnt=0;
    cin>>t;
    while(t--)
    {
       cin >> n>>m;
       cnt++;
       sum=0;
       Build(1,n,1);//构建全是1的树(初始化)
       for(ll i=0;i<=m-1;i++)//共m组数据
       {
           cin>>x;
           Update(x, 1, n, 1);//查找并修改
       }
       printf("Case %lld: %lld\n",cnt,sum);

    }

    return 0;
}

看到网上还有一种更好的方法,这种方法就不用回溯了。边查找变更新。
我也自己敲了遍,确实快了不少。
除了36 ——40这几行发生变化,其余的不发生变化

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
#define ll long long
const ll maxn = 1e7 + 5;
ll sum,n,m;
ll b[maxn*4], a[maxn],ans[maxn];
void push_up(ll rt)//就是简单的向上更新
 {
    b[rt] = b[rt << 1] + b[rt << 1 | 1] ;
}
void Build(ll l,ll r,ll rt)//创建树
{
    //如果达到叶节点
    if(l == r)
    {
        b[rt]=1;
        return ;
    }
    ll m=(l+r)>>1;
    //左右递归;

    Build(l,m,rt<<1);
    Build(m+1,r,rt<<1|1);
    push_up(rt);
}
void Update(ll L, ll l, ll r, ll rt)
//b[rt]--,就是减去这个1后的更新操作,
//提前更新,
{
     b[rt]-- ;
     //
     //
     //
     //每次找出一个数,总和总会减掉1,每搜索到下一行时,就将父节点-1;
    if(l == r)
    {
       sum += l;
        return ;
    }
    int mid = l + r >> 1;
    if(L <= b[rt<<1])
        Update(L, l, mid, rt << 1);
    else
        Update(L-b[rt<<1], mid + 1, r, rt << 1 | 1);

}

int main()
{
    ll t,x,cnt=0;
    cin>>t;
    while(t--)
    {
       cin >> n>>m;
       cnt++;
       sum=0;
       Build(1,n,1);//构建全是1的树(初始化)
       for(ll i=0;i<=m-1;i++)//共m组数据
       {
           cin>>x;
           Update(x, 1, n, 1);
       }
       printf("Case %lld: %lld\n",cnt,sum);

    }

    return 0;
}

B—KPI

在这里插入图片描述
这道题我找到了一种简单的方法:利用队列来模拟出(out)入(in)过程。数组代表第n大的数
因为数组不多的原因,直接c了,但要是数组足够长,这代码就爆了,这代码复杂度O(mn)(应该是吧,我不是很会算) 所以最好使用线段树,我会更新的。

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
using namespace std;

const int  maxn = 1e7 + 5;
int  sum,n,m;
int  b[maxn*4];//此题数在b中是从小到大的顺序的排列的,用来找到第n大的数。
int a[maxn],ans[maxn];
queue<int >q;//队列,模拟出入
int direction(int  x)//找到x在数组应该b[i]中的位置。以便在数组b中添加此数,或除去此数;
{
    int  l=0,r=sum;
    while(l<r)
    {
        int mid=l+r>>1;
        if(b[mid]>=x)
            r=mid;
        else
            l=mid+1;
    }
    return l;
}
void in (int x)//在数组中添加此数。
{
    int k=direction(x);//找到x在b中的下标
    for(int i=sum+1;i>k;i--)
    {
        b[i]=b[i-1];
    }
    b[k]=x;
    sum++;

}
void out(int x)//在数组中删去此数,并将后面的数往前移动一位
{
   int k=direction(x);//找到x在b中的下标
   for(int i=k;i<=sum-1;i++)
   {
       b[i]=b[i+1];
   }
   sum--;
}
int main()
{
     int n,m;
     char s[10];
     int cot=1;
     while(~scanf("%d",&n))
     {
         while(!q.empty())
            q.pop();
         sum=0;
		printf("Case #%d:\n", cot++);
		for(int i=0;i<=n-1;i++)
        {
            scanf("%s",s);
            if(s[0]=='i')
            {
                scanf("%d",&m);
                q.push(m);
                in(m);
            }
            else if(s[0]=='o')//把队头元素出队列
            {
                int t=q.front();
                q.pop();
                out(t);
            }
            else
                printf("%d\n",b[sum>>1]);//查询函数
        }

     }
    return 0;
}

在写第三题之前,关于lower_bound的使用我觉得有必要讲一下

C题中lower_bound的补充

用法

 int t=lower_bound(a+l , a+r, m) -a;
 //函数的作用是数组a中查找[l,r)中区间中值为m的元素。并返回的下标.需要记住后面有个  - a;

特殊情况

1—如果m在区间没有出现过,那就返回第一个比m大的元素的下标;
2—如果区间内有多个m,则返回第一个m的下标。
3—如果m比区间内所有数都大,那么返回r,这时候会越界;小心
时间复杂度为logn,n为数组长度。

C题中erase用法的补充

用法

erase有很多种用法;我也懒得去复制那些专业术语。
直接举例理解:
第一种:

string str = "hello c++! +++";
//erase可以有1个参数,也可以有2个参数。
//当只有一个参数时,意思是从n开始一直删到最后。
str.erase(10);  // 从位置pos=10处开始删除,直到结尾
//两个参数时,意思是从n开始删除m个字符
str.erase(6, 4);  // 从位置pos=6处开始,删除4个字符

第二种;(就是带().end或者().begin()啥的那种)
删除迭代器[first, last)区间的所有字符,返回一个指向被删除的最后一个元素的下一个字符的迭代器.????

string str = "hello c++! +++";
//当只有一个参数时,意思是删除这个位置的元素(从0开始)
str.erase(str.begin()+10);// 删除"+++"前的一个空格
//当有两个参数时,意思是,从第n个元素开始,删到"字符串的结尾(str.end())"//这个是可以更改的;
str.erase(str.begin() + 10, str.end());// 删除"+++"

C题中unique用法的补充

用法

1、unique是STL中常用的函数,他的功能是去重,相邻间元素的去重(所以使用前一般先排序),此处的删除并不是真正的删除,而是重复元素的位置被不重复的元素给占领了。unique的实质就是不停地把后面不重复的元素不断移动到前面来。(只替换对应元素,其他不变)
2、unique通常和erase一起使用来达到删除重复元素的目的(此时的删除是真正的删除,即从容器中去除重复元素),容器长度也发生了变化
new_end=unique(a.begin(),a.end());(前闭后开不包括a.end()的元素),new_end是第一个重复数字的地址。
例子:

vector<int >a={1,3,3,4,5,6,6,6,7};
new_end=unique(a.begin(),a.end());
//数组为134567667;new_end返回的是第2个6的地址(第一个重复的是6)
a.erase(new_end(),a.end());//删掉后面的元素
//数组为134567;
所以一般写为a.erase(unique(a.begin(),a.end()),a.end());

C—Atlantis

在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
#include<vector>
using namespace std;
#define ll long long
const int N=100010;
struct segment//用来存线段信息
{
    double x,y1,y2;
    int d;//区分他是该矩阵前面的线段还是后面的线段
    bool operator < (const segment&t)const
    {
        return x<t.x;
    }
    //换个方法也能写
    //  friend bool operator < (segment&a,segment&b)
    //  {
    //   return a.x<b.x;
    // }
    //
}seg [N<<1];//每个矩阵需要存两个线段
//线段树的每个节点 保存的为线段,0号点为y[0]到y[1],以此类推

struct node
{
    int l,r;//记录区间的左右
    int cnt;//记录区间出现过几次;
    double len;//记录这段区间的长度
}tr[N<<3];
vector<double >ys;
int n;
int find(double y)
{
    //需要返回vector中第一个>=y的数的下标;
    return lower_bound(ys.begin(),ys.end(),y)-ys.begin();
}


void pushup( int u)
{
    //例如:假设tr[1].l=0,tr[1].r=1;
    //       ys[0]为y【0】 到y[1]的距离,ys【1】为y[0]到y[1]的距离
    //       tr[1].len等于y[0]到y[2]的距离
    //       y[2]=ys[tr[1].r+1],y[0]=ys[tr[1].l].
    if(tr[u].cnt)//计数器>0,证明可以算
    tr[u].len=ys[tr[u].r+1]-ys[tr[u].l];//表示整个区间都被覆盖
    //该段长度就为右端点 + 1后在ys中的值 - 左端点在ys中的值
    //
    //如果tr[u].cnt=0;一共有两种情况
    //1.完全覆盖,这种情况由modify的第一个if进入
    //     这时就意味着把完整的l,r贡给len的部分清除掉
    //     留下其他可能存在的子区间对len的贡献,这是一种现象
    //2.不完全覆盖:由modify的最后一行的else进入;
    //     表示区间并不是被完全覆盖,可能部分被覆盖,需要通过子节点来更新
    else if(tr[u].l!=tr[u].r)
    {
        tr[u].len=tr[u<<1].len+tr[u<<1|1].len;
    }
    else
        tr[u].len=0;//表示叶子节点且该线段完全被覆盖。为无用线段n,长度变为0;·
}

void modify(int u,int l,int r,int d)//表示从线段树中l点到r点的出现次数+d
{
    if(tr[u].l>=l&&tr[u].r<=r)//区间被完全覆盖
    {
        tr[u].cnt+=d;///该区间出现的次数增加d;
        pushup(u);//更新len
    }
    else
        {
            int mid=tr[u].l+tr[u].r>>1;
            if(l<=mid)
              modify(u << 1,l,r,d);//左边存在点
            if(r>mid)
            modify(u << 1 | 1,l,r,d);//右边存在点
            pushup(u);
        }
}
void build(int u,int l,int r)
{
    tr[u]={l,r,0,0};
    if(l!=r)
    {
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        //后面都为0,不用更新len。
    }
}
int main()
{
    int T=1;
    while(cin>>n,n)//多组输入
    {
        ys.clear();
        int j=0;//一共出现过j个线段
        for(int i=0;i<n;i++)//处理输入
        {
            double x1,x2,y1,y2;
            cin>>x1>>y1>>x2>>y2;
            seg[j++]={x1,y1,y2,1};//前面出现的线段
            seg[j++]={x2,y1,y2,-1};//后面出现的线段
            ys.push_back(y1);
            ys.push_back(y2);//y轴出现过那些点。
        }
        sort(seg,seg+j);//线段按x排序
        sort(ys.begin(),ys.end());//先排序
        ys.erase(unique(ys.begin(),ys.end()),ys.end());//离散化去重
         //例子:假设现在有三个不同的y轴点,分为两个线段
        //y[0] ~ y[1],y[1] ~ y[2];
        //此时ys.size()为3,ys.size() - 2 为 1;
        //此时为 build(1, 0, 1);
        //有两个点0 和 1,线段树中0号点为y[0] ~ y[1],1号点为y[1] ~ y[2];
       build(1,0,ys.size()-2);
       double res=0;
       for(int i=0;i<j;i++)
       {
            //根节点的长度即为此时有效线段长度 ,再 * x轴长度即为面积
            if (i)res += tr[1].len * (seg[i].x - seg[i - 1].x);
            //处理一下该线段的信息,是加上该线段还是消去
            //例子:假设进行modify(1,find(10),find(15) - 1,1);
            //      假设find(10) = 0,find(15) = 1;
            //      此时为modify(1, 0, 0, 1);
            //      表示线段树中0号点出现次数加1;
            //      而线段树中0号点刚好为线段(10 ~ 15);
            //      这就是为什么要进行find(seg[i].y2) - 1 的这个-1操作
           modify(1,find(seg[i].y1), find(seg[i].y2) - 1,seg[i].d);
       }
        printf("Test case #%d\n", T ++ );
        printf("Total explored area: %.2lf\n\n", res);

    }
    return 0;
}

D—覆盖的面积

在这里插入图片描述
在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
#include<vector>
#include<math.h>
using namespace std;
#define ll long long
const int N=100010;
struct segment//用来存线段信息
{
    double x,y1,y2;
    int d;//区分他是该矩阵前面的线段还是后面的线段
    bool operator < (const segment&t)const
    {
        return x<t.x;
    }

}seg [N<<1];//每个矩阵需要存两个线段
//线段树的每个节点 保存的为线段,0号点为y[0]到y[1],以此类推

struct node
{
    int l,r;//记录区间的左右
    int cnt;//记录区间出现过几次;
    double len,len2;//记录这段区间的长度

}tr[N<<3];
vector<double >ys;
int n;
int find(double y)
{
    //需要返回vector中第一个>=y的数的下标;
    return lower_bound(ys.begin(),ys.end(),y)-ys.begin();
}


void pushup( int u)
{
    //例如:假设tr[1].l=0,tr[1].r=1;
    //       ys[0]为y【0】 到y[1]的距离,ys【1】为y[0]到y[1]的距离
    //       tr[1].len等于y[0]到y[2]的距离
    //       y[2]=ys[tr[1].r+1],y[0]=ys[tr[1].l].
    if(tr[u].cnt)//计数器>0,证明可以算
    tr[u].len=ys[tr[u].r+1]-ys[tr[u].l];//表示整个区间都被覆盖
    //该段长度就为右端点 + 1后在ys中的值 - 左端点在ys中的值
    //
    //如果tr[u].cnt=0;一共有两种情况
    //1.完全覆盖,这种情况由modify的第一个if进入
    //     这时就意味着把完整的l,r贡给len的部分清除掉
    //     留下其他可能存在的子区间对len的贡献,这是一种现象
    //2.不完全覆盖:由modify的最后一行的else进入;
    //     表示区间并不是被完全覆盖,可能部分被覆盖,需要通过子节点来更新
    else if(tr[u].l!=tr[u].r)
    {
        tr[u].len=tr[u<<1].len+tr[u<<1|1].len;
    }
    else
        tr[u].len=0;

    if(tr[u].cnt>=2)
        tr[u].len2=ys[tr[u].r+1]-ys[tr[u].l];
    else if(tr[u].l==tr[u].r)
    {
        tr[u].len2=0;
    }
    else
        if(tr[u].cnt==1)
        tr[u].len2=tr[u<<1].len+tr[u<<1|1].len;
    else
        tr[u].len2=tr[u<<1].len2+tr[u<<1|1].len2;

}

void modify(int u,int l,int r,int d)
{
    if(tr[u].l>=l&&tr[u].r<=r)//区间被完全覆盖
    {
        tr[u].cnt+=d;
        pushup(u);//更新len
    }
    else
        {
            int mid=tr[u].l+tr[u].r>>1;
            if(l<=mid)
              modify(u << 1,l,r,d);//左边存在点
            if(r>mid)
            modify(u << 1 | 1,l,r,d);//右边存在点
            pushup(u);
        }
}
void build(int u,int l,int r)
{
    tr[u]={l,r,0,0};
    if(l!=r)
    {
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        //后面都为0,不用更新len。
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {

       cin>>n;
        ys.clear();
        int j=0;
        for(int i=0;i<n;i++)
        {
            double x1,x2,y1,y2;
            cin>>x1>>y1>>x2>>y2;
            seg[j++]={x1,y1,y2,1};
            seg[j++]={x2,y1,y2,-1};
            ys.push_back(y1);
            ys.push_back(y2);
        }
        sort(seg,seg+j);
        sort(ys.begin(),ys.end());
        ys.erase(unique(ys.begin(),ys.end()),ys.end());//离散化去重
       build(1,0,ys.size()-2);
       double res=0;
       for(int i=0;i<j;i++)
       {
           if(i)
           {
               res+=tr[1].len2*(double)(seg[i].x-seg[i-1].x);
           }
           modify(1,find(seg[i].y1), find(seg[i].y2) - 1,seg[i].d);
       }
      /* int yyy;
       yyy=(int )(res*1000);
       if(yyy % 10>=5)
        yyy=yyy-(yyy%10)+10;
       res=(double)yyy/1000;*/
        printf("%.2lf\n", res);

    }
    return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e7+5;
#define ll long long
struct tree
{
    int l;//区间左端
    int r;//区间右端
    int date;//当前区间被覆盖的长度
    int add;//统计被覆盖了多少次;
} t[N<<3];
struct point
{
    int x;
    int y1;
    int y2;
    int flag;//标记符,用来存左边还是右边,左侧就存1,右边就存-1
} p[N<<1]; //用来存点的
int temp[N][4];//用来存的点的
int val[N<<1];//用来存离散化的
int n;
bool compare (point a,point b)
{
    if(a.x<b.x)
        return a.x<b.x;
    else if(a.x==b.x&&a.flag>b.flag) //flag大的放在前面
        return true;
    else
        return false;
}
void pushup(int rt)
{
    if(t[rt].add)
        t[rt].date=val[t[rt].r]-val[t[rt].l];//离散化后的左边减右端
    else
        t[rt].date=t[rt<<1].date+t[rt<<1|1].date;//向上更新(一次也没有覆盖)
}
void build(int rt,int l,int r)
{
    //赋值
    t[rt].l=l;
    t[rt].r=r;
    t[rt].date=0;
    t[rt].add=0;
    if(r == l + 1)
        return ;
    int mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid,r);
}
void update (int rt,int l,int r,int c)//查找某点并进行更新
{
    if(l<=t[rt].l&&r>=t[rt].r)
        t[rt].add+=c;
    else
    {
        if(l<t[rt<<1].r)
            update(rt<<1,l,r,c);
        if(r>t[rt<<1|1].l)
            update(rt<<1|1,l,r,c);
    }
    pushup(rt);
    return ;
}
int f()//扫描线,扫x,或扫y
{
    sort(val+1,val+(n<<1|1));//排完序,除去相邻间的相同元素,如果用vector可以和erase去掉其中多余元素
    //长度就自动为val.size();
    int cnt=unique(val+1,val+(n<<1|1))-(val+1);//离散化加去重排序
    build(1,1,cnt);
    sort(p+1,p+(n<<1|1),compare);//按照从小到大的顺序进行排序
    ll ans=0,last=0;//ans用来存答案,last用来存边后的长度

    for(int i=1; i<=(n<<1); i++)
    {
        int l = lower_bound(val+1,val+cnt+1,p[i].y1)-val;
        int r = lower_bound(val+1,val+cnt+1,p[i].y2)-val;//查找y对应的离散化
        update(1,l,r,p[i].flag);
        ans+=abs(last-t[1].date);//绝对值
        last=t[1].date;//更新last
    }
    return ans;


}
int main()
{
    int a,b,c,d;
    while(~scanf("%d",&n))
    {
        for(int i=1; i<=n; i++)
        {
            scanf("%d%d%d%d",&a,&b,&c,&d);
            p[i].x=a;
            p[i+n].x=c;
            p[i].y1=p[i+n].y1=b;
            p[i].y2=p[i+n].y2=d;
            p[i].flag = 1;
            p[i+n].flag = -1;
            val[i]=b;
            val[i+n]=d;
            temp[i][0]=a;
            temp[i][1]=b;
            temp[i][2]=c;
            temp[i][3]=d;

        }
        ll ans=0;
        ans=f();//水平的长
        for(int i=1; i<=n; i++)
        {
            b=temp[i][0];
            a=temp[i][1];
            d=temp[i][2];
            c=temp[i][3];
            p[i].x=a;
            p[i+n].x=c;
            p[i].y1=p[i+n].y1=b;
            p[i].y2=p[i+n].y2=d;
            p[i].flag = 1;
            p[i+n].flag = -1;
            val[i]=d;
            val[i+n]=b;

        }
        ans+=f();//竖直的长
        printf("%lld\n",ans);
    }
    return 0;

}

F - The Skyline Problem

在这里插入图片描述

#include <cstdio>
#include<iostream>
#include<cstring>
#include <algorithm>
const int maxn = 10005;
using namespace std;
int high[maxn];
int maxx(int a,int b)
{
    return a>b?a:b;
}
int main()
{
    int L,R,longg,High;
    while(scanf("%d%d%d",&L,&High,&R)==3)
    {
        for(int  i=L; i<R; i++)
        {
            high[i]=maxx(high[i],High);
            longg=maxx(longg,i);
        }
    }
    for(int i=1; i<=longg; i++)
    {
        if(high[i]!=high[i-1])
            printf("%d %d ",i,high[i]);
    }
    printf("%d 0\n",longg+1);
     return 0;
}

G—Frequent values

在这里插入图片描述

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e6+5;
#define ll long long
struct node
{
    int l;//区间左边
    int r;//区间右边
    int maxs;//最长连续个数
    int left;//左边连续个数
    int right;//右边连续个数
} t[N<<1];
int n,m,x,y,p[N];
void build(int rt,int l,int r)//建树
{
    t[rt].l=l;
    t[rt].r=r;
    if (l == r)
    {
        t[rt].left=1;
        t[rt].right=1;
        t[rt].maxs=1;
        return;
    }
    int mid=(l+r)>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);//初始化为全为1;



    if (t[rt<<1].left==mid-l+1&&p[mid]==p[mid+1])
        t[rt].left=t[rt<<1].right+t[rt<<1|1].left;//如果左边的所有数都等于右边最左的数(左边连续长度增加)
    else
        t[rt].left=t[rt<<1].left;



    if (t[rt<<1|1].right==r-mid&&p[mid]==p[mid+1])//如果右树的所有数都等于左树最右边的数
        t[rt].right=t[rt<<1].right+t[rt<<1|1].left;//右边连续的长度=右儿子+左儿子的连续树个数
    else
        t[rt].right=t[rt<<1|1].right;//左儿子不是全连续的,右儿子也不是全连续的(就是最优解在中间的情况),就只加右儿子连续部分



    if (p[mid]==p[mid+1])//如果右边最左的书等于右边最左的数
    {
        int u=max(t[rt<<1].maxs,t[rt<<1|1].maxs);//中间的最大连续长度
        t[rt].maxs=max(u,t[rt<<1].right+t[rt<<1|1].left);//和左右的最长的最大值作比较;
    }

    else
        t[rt].maxs=max(t[rt<<1].maxs,t[rt<<1|1].maxs);//否则中间一定是最优解
}
int queny(int rt,int l,int r)
{
    int max1=1,max2=1,max3=1;
    if (t[rt].l>r||t[rt].r<l||t[rt].r<t[rt].l) //若r,l不在范围内
        return 0;

    if (t[rt].r<=r && t[rt].l>=l)//如果全部包括在内部
        return t[rt].maxs;

    int mid=(t[rt].r+t[rt].l)>>1;
    if (p[mid]==p[mid+1])
        max3=min(mid-l+1,t[rt<<1].right)+min(r-mid,t[rt<<1|1].left);//中间的最大连续长度
    if (l<=mid)
        max1=queny(rt<<1,l,r);//左边的最大连续长度
    if (r>mid)
        max2=queny(rt<<1|1,l,r);//右边的最大连续长度

    max3=max(max3,max(max1,max2));//全部的的最大连续长度
    return max3;
}
int main()
{
    int i;
    while(scanf("%d",&n)&&n)
    {
        scanf("%d",&m);
        for (i=1; i<=n; i++)
            scanf("%d",&p[i]);
        memset(t,0,sizeof(t));
        build(1,1,n);


        for(i=1; i<=m; i++)
        {
            scanf("%d%d",&x,&y);
            ll  ans=queny(1,x,y);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

I—Hotel

在这里插入图片描述
在这里插入图片描述

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define ls (rt<<1)
#define rs (rt<<1|1)
//1为空房
using namespace std;
const int N=111110;
struct  Hotel
{
    int lsum;//左侧最长1序列的长度
    int rsum;//右侧最长1序列的长度
    int sum;//全部最长1序列的长度
    int cover;//颜色,cover=1证明这个房间可以用

}hotel[N<<2];
void pushUp(int rt,int m)//区间赋值需要区间长度做参数,所以,这里需要一个m;
{
    hotel[rt].lsum=hotel[ls].lsum;//向上继承
    hotel[rt].rsum=hotel[rs].rsum;//向上继承

    if(hotel[rt].lsum==m-(m>>1))//所谓的左儿子为空事实上是左儿子等于区间长度,m为区间长度,m>>1为右区间长度
    hotel[rt].lsum+=hotel[rs].lsum;//需要加上右儿子的左lsum(左边全部是空的)
   if(hotel[rt].rsum==(m>>1))//所谓右儿子为空的事实上是右儿子等于区间长度
        hotel[rt].rsum+=hotel[ls].rsum;//需要加上左儿子的rsum
    hotel[rt].sum=max(hotel[ls].rsum+hotel[rs].lsum,max(hotel[ls].sum,hotel[rs].sum));
     //比较:放在左儿子的那个区间,放在右儿子的那个区间,和放在左儿子和右儿子中间(左区间右部分和右区间的左半部分)
}
void Build(int l,int r,int rt)
{
     hotel[rt].lsum=hotel[rt].rsum=hotel[rt].sum=r-l+1;//一开始全是1的序列
     hotel[rt].cover = -1;//当前区间已经初始化,不用向下更新
     if(l == r )
            return ;//通过二分法找到相应的点
     int m=(l+r)>>1;//找不到就通过二分法继续向下建树
     Build(l,m,ls);
     Build(m+1,r,rs);

}
void pushDown(int rt,int m)
{
    if(hotel[rt].cover!=-1)//在该点值已经变化的情况下,再向下更新
    {
        hotel[rs].cover=hotel[ls].cover=hotel[rt].cover;
        hotel[ls].sum=hotel[ls].lsum=hotel[ls].rsum=hotel[rt].cover?0:m-(m>>1);
        hotel[rs].sum=hotel[rs].lsum=hotel[rs].rsum=hotel[rt].cover?0:(m>>1);
        hotel[rt].cover=-1;//更新完了就变成-1,证明不必再向下更新,避免重复

    }
}
void update(int L,int R,int C,int l,int r,int rt)
{
    if(L<=l&&R>=r)//区间[l,r)刚好在L,R之间,就把该地全变成0,证明已经入住,或者把变成r-l+1,证明已经退房
    {
        hotel[rt].cover=C;
        hotel[rt].sum=hotel[rt].rsum=hotel[rt].lsum=hotel[rt].cover?0:r-l+1;
        return ;
    }
    int m=l+r>>1;
    pushDown(rt,r-l+1);//向下更新
    if(L<=m)
        update(L,R,C,l,m,ls);
    if(R>m)
        update(L,R,C,m+1,r,rs);
    pushUp(rt,r-l+1);//回溯向上更新
}
int query(int w,int l,int r,int rt)
{
    if(l==r)
        return l;
    pushDown(rt,r-l+1);
    int m=l+r>>1;
    if(hotel[ls].sum>=w)
        return query(w,l,m,ls);
    else
     if(hotel[ls].rsum+hotel[rs].lsum>=w)
        return m-hotel[ls].rsum+1;
    return query(w,m+1,r,rs);
}
int main()
{
    int n,k;
    while(~scanf("%d%d",&n,&k))
    {
        Build(1,n,1);
        while(k--)
        {
            int op,a,b;
            scanf("%d%d",&op,&a);
            if(op==1)
            {
                if(hotel[1].sum<a)
                {
                    printf("0\n");
                    continue;
                }
                int p=query(a,1,n,1);
                printf("%d\n",p);
                update(p,p+a-1,1,1,n,1);
            }
            else
            {
                scanf("%d",&b);
                update(a,a+b-1,0,1,n,1);
            }
        }
    }
    return 0;
}

J—花神游历各国

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
#define ll long long
//typedef long long ll;
using namespace std;

const int  maxn = 1e5 + 5;
struct Node
{
    int l,r;
    ll sum;
    int f;
} b[maxn*4];
int a[maxn];
int m,n,cnt=0;
void push_up(int  rt)
{
    b[rt].sum = b[rt << 1].sum + b[rt << 1 | 1].sum ;
    b[rt].f= (b[rt << 1].f && b[rt << 1 | 1].f );
}

void Build(int rt,int  l,int r)//创建树
{
    //如果达到叶节点
    if(l == r)
    {
        b[rt]= {l,l,a[l],(a[l]<=1)};
    }
    else
    {

        b[rt]= {l,r};
        int  m=(l+r)>>1;
        //左右递归;

        Build(rt<<1,l,m);
        Build(rt<<1|1,m+1,r);
        push_up(rt);
    }
}
void Update(ll l, ll r, ll rt)
{
    if(b[rt].f)//单点修改的优化
        return ;
    if(b[rt].l == b[rt].r)
    {
        b[rt].sum=sqrt(b[rt].sum);
        b[rt].f=b[rt].sum<=1;
    }
    else
    {

        int mid = (b[rt].l + b[rt].r )>> 1;
        if(r<= mid)
            Update(l, r, rt << 1);
        else if(l>mid)
            Update( l, r, rt << 1 | 1);
        else
        {
            Update( l, mid, rt << 1 );
            Update( mid + 1, r, rt << 1 | 1);
        }
        push_up(rt);
    }
}
ll query(int rt,int l,int r)
{
    if(b[rt].l>=l&&b[rt].r<=r) return b[rt].sum;
    int mid = (b[rt].l+b[rt].r)>>1;
    ll res=0;
    if(l<=mid) res+=query(rt<<1,l,r);
    if(r>mid) res+=query(rt<<1|1,l,r);
    return res;
}
int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
        scanf("%d",&a[i]);
    scanf("%d",&m);
    Build(1,1,n);
    int x,l,r;
    while(m--)
    {
        scanf("%d%d%d",&x,&l,&r);
        if(x==1)
            printf("%lld\n",query(1,l,r));
        else
            Update(l,r,1);
    }


    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值