[uoj164][清华集训2015]V——线段树

题目大意:

传送门

思路:

对于这么多的操作,以及询问时的取历史最大值,用一般的线段树显然不太好做。
于是考虑把每个操作转化成\(h_i=\max(h_i+a,b)\)的形式,不难发现第一种和第二种就是\(h_i=\max(h_i+x,0)\),第三种即\(h_i=\max(h_i-inf,x)\)。
于是我们在线段树上对于每一个节点维护这两个标记,考虑如何合并标记:
\[\begin{aligned}
x &=\max(\max(x+a,b)+a',b')\ & =\max(\max(x+a+a',b+a'),b')\ & =\max(x+a+a',\max(b+a',b'))\\end{aligned}\]
这里假设\(a,b\)是原来的标记,\(a',b'\)是新加上的标记。
考虑如何求历史的最大值,对于一个点的最大值\(\max(x+a,b)\),要么在左边取到,要么在右边取到,我们只需要记录左边的最大值和右边的最大值即可,即记录\(ma=\max a,mb=\max b\)。
同时\(ma,mb\)也是需要合并的,具体的式子自己推导一下就好了。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<" "
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
    freopen("uoj164.in","r",stdin);
    freopen("uoj164.out","w",stdout);
}

template<typename T>void read(T &_){
    _=0; T f=1; char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())_=(_<<1)+(_<<3)+(c^'0');
    _*=f;
}
template<typename T>void chkmax(T &_,T __){_= __>_ ? __ : _;}

const int maxn=5e5+10;
const ll inf=1e18;
int n,m;
ll w[maxn];

struct seg{
    ll a,b,ma,mb;
    void down(seg t){
        ma=max(ma,a+t.ma); mb=max(mb,max(b+t.ma,t.mb));
        a=max(a+t.a,-inf); b=max(b+t.a,t.b);
    }
    void reset(){a=b=ma=mb=0;}
}c[maxn<<2];

struct Segment_Tree{
#define mid ((l+r)>>1)
#define lc (o<<1)
#define rc (o<<1|1)
#define lson lc,l,mid
#define rson rc,mid+1,r
    void build(int o,int l,int r){
        if(l==r)c[o]=(seg){w[l],0,w[l],0};
        else build(lson),build(rson);
    }
    void update(int o,int l,int r,int L,int R,seg x){
        if(L<=l && r<=R)c[o].down(x);
        else{
            c[lc].down(c[o]); c[rc].down(c[o]);
            c[o].reset();
            if(L<=mid)update(lson,L,R,x);
            if(R>=mid+1)update(rson,L,R,x);
        }
    }
    ll query(int o,int l,int r,int p,int ty){
        if(l==r){
            if(!ty)return max(c[o].a,c[o].b);
            return max(c[o].ma,c[o].mb);
        }
        else{
            c[lc].down(c[o]); c[rc].down(c[o]);
            c[o].reset();
            if(p<=mid)return query(lson,p,ty);
            else return query(rson,p,ty);
        }
    }
}T;

int main(){
    File();
    read(n),read(m);
    REP(i,1,n)read(w[i]);
    T.build(1,1,n);
    int ty,l,r;
    ll x;
    REP(i,1,m){
        read(ty);
        if(ty<=3){
            read(l),read(r),read(x);
            if(ty==1)T.update(1,1,n,l,r,(seg){x,0,x,0});
            if(ty==2)T.update(1,1,n,l,r,(seg){-x,0,-x,0});
            if(ty==3)T.update(1,1,n,l,r,(seg){-inf,x,-inf,x});
        }
        else{
            read(l);
            printf("%lld\n",T.query(1,1,n,l,ty-4));
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/ylsoi/p/10215859.html

时间: 2024-11-10 14:36:29

[uoj164][清华集训2015]V——线段树的相关文章

清华集训2015 V

#164. [清华集训2015]V http://uoj.ac/problem/164 统计 描述 提交 自定义测试 Picks博士观察完金星凌日后,设计了一个复杂的电阻器.为了简化题目,题目中的常数与现实世界有所不同. 这个电阻器内有编号为 1∼n1∼n 的 nn 个独立水箱,水箱呈圆柱形,底面积为 1 m21 m2,每个水箱在顶部和底部各有一个阀门,可以让水以 1 m3/s 的流量通过,每个水箱的上阀门接水龙头,可以无限供应水,下阀门不接东西,可以让水流出.水箱顶部和底部都有一个接口,水的电

UOJ #164 【清华集训2015】V (线段树)

题目链接 http://uoj.ac/problem/164 题解 神仙线段树题. 首先赋值操作可以等价于减掉正无穷再加上\(x\). 假设某个位置从前到后的操作序列是: \(x_1,x_2,...,x_k\) 那么则有: 当前值就是该序列的最大后缀和,历史最大值就是该序列的最大子段和! 然后如果把最大子段和定义加法,那么就变成了区间加单点查询. 直接线段树维护即可,时间复杂度\(O(n\log n)\). (好吧,其实似乎把赋值看做减去正无穷再加\(x\)似乎是可以被卡爆long long的-

暑假集训--线段树

线段树 线段树的每个结点都代表一个区间. 线段树有唯一的根节点代表整个范围,比如:[1,N]; 线段树的每个叶子结点都代表一个长度为1的元区间 [x,x]; 对于每个内部节点[l,r],它的左节点是[l,m],右节点是[m+1,r],其中m=(l+r)/2(向下取整) 图例说明: 该线段树存储的是[0,7]区间的相应信息. 可以得出线段树大约有2n个结点,深度为O(logn) 一般采用数组或结构体的方式存储,编号为i的结点的左儿子为2*i,右儿子为2*i+1 每个结点维护对应区间的和 建树(这里

BZOJ 3747 POI 2015 Kinoman 线段树

题目大意:给出电影院的放映电影顺序,一个电影只有看过一次的时候会获得电影的权值.没看过或者看两次或以上都不能获得权值.问看连续区间的电影能够获得的最大权值是多少. 思路:利用线段树维护前缀和.将出现第一次的地方的权值加上那部电影的权值,第二次出现的时候权值减去那部电影的权值.枚举起点,先更新答案,然后在当前节点减去权值的二倍,然后再在下一次出现的地方加上权值(我感觉我没说明白,总之看代码吧... CODE: #include <cstdio> #include <cstring>

uoj164. 【清华集训2015】V 统计

坑爹题面:http://uoj.ac/problem/164 正常题面: 对于一个序列支持下列5个操作: 1.区间加x 2.区间减x并与0取max 3.区间覆盖 4.单点查询 5.单点历史最大值查询 题解: 每个区间维护一个标记函数f(x)=max(x+a,b) 那么两个标记 f 和 g 的合并就是f(g(x))=max(x+max(fa+ga,-inf),max(fb+ga,gb))(假设f在前g在后) 区间加减就是打上max(x,0),区间覆盖就是打上max(-inf,x) 只要记录历史最大

UOJ #164 【清华集训2015】 V

题目链接:V 这道题由于是单点询问,所以异常好写. 注意到每种修改操作都可以用一个标记\((a,b)\)表示.标记\((a,b)\)的意义就是\(x=\max\{x+a,b\}\) 同时这种标记也是支持合并的.有\((a,b)+(c,d)=(a+c,\max\{b+c,d\})\) 用上这种标记的话,\(1\)操作就是\((x,0)\),\(2\)操作就是\((-x,0)\),\(3\)操作就是\((-inf,x)\). 要查询单点值的话只要把所有标记都下放了就好了. 这种标记也支持取\(\ma

#163. 【清华集训2015】新式计算机

//题目太长,为了我的小心脏,我都没忍心读下去...... 这是一道提交答案题. 伟大的人类智慧之神 clevertick 奋战三星期,造出了一台新式计算机(其实他造计算机只用了三天,剩余的时间都在编译.调试……),起名 CTOX.现在,他可以抛弃自己旧的机器,来用 CTOX 完成自己的工作了…… CTOX 拥有足够大(可以认为是无穷大)的内存,并且它的内存地址是二维的,也就是说,一个内存地址可以使用一对整数 (x,y)(x,y) 来表示,和这个地址相邻的4个地址分别是 (x+1,y)(x+1,

SPOJ GSS5 Can you answer these queries V ——线段树

[题目分析] GSS1上增加区间左右端点的限制. 直接分类讨论就好了. [代码] #include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <map> #include <set> #include <queue> #include <string> #include <iostream> #i

Spoj 2916 Can you answer these queries V 线段树 求任意重叠区间的最大子段和

题目链接:点击打开链接 题意: T个测试数据 n个数字 q个询问 每个询问 : [x1, y1] [x2, y2] 问: int ans = -inf; for(int i = x1; i <= y1; i++) for(int j = max(x2, i); j <= y2; j++) ans = max(ans, query(i, j)); 思路: query_L(int l, int r) 求的是在区间[l,r]内 ,左端点为l的最大子段和. 其他和GSS3差不多. 分类讨论一下给定的区