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

题目链接

http://uoj.ac/problem/164

题解

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

代码

#include<bits/stdc++.h>
#define llong long long
using namespace std;

const int N = 5e5;
void updsum(llong &x,llong y) {x = x>=y?x:y;}
struct Data
{
    llong s,ls,rs,lrs;
    Data() {}
    Data(llong x) {lrs = x; x = x<0ll?0ll:x; s = ls = rs = x;}
    Data(llong _s,llong _ls,llong _rs,llong _lrs):s(_s),ls(_ls),rs(_rs),lrs(_lrs) {}
    bool operator ==(const Data &arg) const {return s==arg.s&&ls==arg.ls&&rs==arg.rs&&lrs==arg.lrs;}
};
Data operator +(const Data &arg1,const Data &arg2)
{
    Data ret(0,0,0,0);
    ret.lrs = arg1.lrs+arg2.lrs;
    ret.ls = max(arg1.ls,arg1.lrs+arg2.ls);
    ret.rs = max(arg1.rs+arg2.lrs,arg2.rs);
    ret.s = max(max(arg1.s,arg2.s),arg1.rs+arg2.ls);
    return ret;
}
llong a[N+3];
int n,q;

struct SegmentTree
{
    Data sgt[(N<<2)+3];
    void pushdown(int u)
    {
        sgt[u<<1] = sgt[u<<1]+sgt[u];
        sgt[u<<1|1] = sgt[u<<1|1]+sgt[u];
        sgt[u] = Data(0);
    }
    void build(int u,int le,int ri,llong a[])
    {
        if(le==ri) {sgt[u] = Data(a[le]); return;}
        int mid = (le+ri)>>1;
        build(u<<1,le,mid,a); build(u<<1|1,mid+1,ri,a);
    }
    void add(int u,int le,int ri,int lb,int rb,llong x)
    {
        if(le>=lb && ri<=rb) {sgt[u] = sgt[u]+Data(x); return;}
        pushdown(u);
        int mid = (le+ri)>>1;
        if(lb<=mid) add(u<<1,le,mid,lb,rb,x);
        if(rb>mid) add(u<<1|1,mid+1,ri,lb,rb,x);
    }
    llong query(int u,int le,int ri,int pos,int typ) //1:cur 2:hist
    {
        if(le==ri) {return typ==1?sgt[u].rs:sgt[u].s;}
        pushdown(u);
        int mid = (le+ri)>>1;
        if(pos<=mid) return query(u<<1,le,mid,pos,typ);
        else return query(u<<1|1,mid+1,ri,pos,typ);
    }
} sgt;

int main()
{
    scanf("%d%d",&n,&q); llong cur = 0ll;
    for(int i=1; i<=n; i++) scanf("%lld",&a[i]),cur = max(cur,a[i]);
    sgt.build(1,1,n,a);
    while(q--)
    {
        int opt; scanf("%d",&opt);
        if(opt==1||opt==2)
        {
            int l,r; llong x; scanf("%d%d%lld",&l,&r,&x); if(opt==1) cur+=x; if(opt==2) x=-x;
            sgt.add(1,1,n,l,r,x);
        }
        else if(opt==3)
        {
            int l,r; llong x; scanf("%d%d%lld",&l,&r,&x);
            sgt.add(1,1,n,l,r,-cur); sgt.add(1,1,n,l,r,x);
        }
        else if(opt==4||opt==5)
        {
            int pos; scanf("%d",&pos);
            llong ans = sgt.query(1,1,n,pos,opt-3); printf("%lld\n",ans);
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/suncongbo/p/12004568.html

时间: 2024-10-10 13:14:54

UOJ #164 【清华集训2015】V (线段树)的相关文章

[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

清华集训2015 V

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

[UOJ 160][清华集训2015]斗地主

文字 数字 较多,加载略慢 题目链接 Emm怎么没人做啊,网上都找不到题解我怎么抄 首先大力模拟,写一个对抗搜索,记录当前玩家的牌,对手的牌,当前玩家,对手上一次出的牌 然后加一个记忆化搜索,这样可以轻松跑出前两个Subtask(当然你可以加上O2优化跑得快一点) 然后我大力跑一下Subtask3,每个点都在\(1s~2s\)左右,然后跑了几十分钟也跑出来了 当然或许是因为i5-8250U然后开一大堆窗口的原因(复杂度爆炸(不过低压U确实慢 然后改成二分答案+判定,这样可以在搜索时加很多剪枝,比

暑假集训--线段树

线段树 线段树的每个结点都代表一个区间. 线段树有唯一的根节点代表整个范围,比如:[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 每个结点维护对应区间的和 建树(这里

uoj #46[清华集训2014]玄学

uoj 因为询问是关于一段连续区间内的操作的,所以对操作构建线段树,这里每个点维护若干个不交的区间,每个区间\((l,r,a,b)\)表示区间\([l,r]\)内的数要变成\(ax+b\) 每次把新操作加入线段树中下一个叶子,然后如果某个节点里所有操作都加进去了,就条到父亲,把两个儿子的信息合并到父亲上.这里合并就是把两个区间集合合并成一个,例如两个区间\([a,c]\)和\([b,d](a\le b\le c\le d)\)会合并成\([a,b),[b,c),[c,d]\).合并出来的区间如果

BZOJ 3747 POI 2015 Kinoman 线段树

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

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

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#228】基础数据结构练习题 线段树

#228. 基础数据结构练习题 题目链接:http://uoj.ac/problem/228 Solution 这题由于有区间+操作,所以和花神还是不一样的. 花神那道题,我们可以考虑每个数最多开根几次就会成1,而这个必须利用开根的性质 我们维护区间最大.最小.和.区间加操作可以直接做. 区间开方操作需要特殊考虑. 首先对于一个区间,如果这个区间的所有数取$x=\left \lfloor \sqrt{x} \right \rfloor$值一样,那么就可以直接区间覆盖. 分析上述过程,一个区间可以