CPU监控 线段树裸题

LINK:bzoj3064 此题甚好码了20min停下来思考的时候才发现不对的地方有点坑...

还真不好写来着 可这的确是线段树的裸题...我觉得我写应该没有什么大问题 不过思路非常的紊乱 如果是自己写的话 所以为了自己能写出来 整理思路就是这篇博客了。

Q X Y:询问从X到Y这段时间内CPU最高使用率 
A X Y:询问从X到Y这段时间内之前列出的事件使CPU达到过的最高使用率 
P X Y Z:列出一个事件这个事件使得从X到Y这段时间内CPU使用率增加Z 
C X Y Z:列出一个事件这个事件使得从X到Y这段时间内CPU使用率变为Z

4个询问 观察如果没有第二个询问的话那么这将是一个线段树维护两个标记且维护区间最大值的操作 很好写。

能否标记永久化?学了就应该思考一下能否标记永久化 好像有点繁杂两个标记互相干扰 不过应该没有什么大问题 但是加上区间最值就不太好写了 所以对于多个标记且互相影响且维护历史最值应该是做不了的 建议pushdown更好一点。

综上 线段树 的lazy tag 和标记永久化都是选自哪一种好写写哪一个,这里推荐写lazy tag代码复杂度不算太高。

考虑如何维护历史最值 这个z可能是负数 如果简单的开一个标记mx表示历史最值的话对于一次区间 的修改 考虑一件事情pushdown未传递之时一个区间覆盖的标记打过来直接add标记被覆盖掉么 那么历史最值可就会出错 其子树内部的最值标记是应该被更新掉的。

现在没有被更新 所以这一点上来看是不太正确的 当然也有另一种情况 区间覆盖标记覆盖原本的标记 这样的话历史最值也会出现一些大问题。如何解决 针对上述情况我想了一个再覆盖标记的时候 先把被覆盖的标记进行下传然后再覆盖标记 让下传的标记先虽然对目前的值是没有什么大影响的但是对历史最值是有可能更新的所以需要这样做 再down中我突然意识到了一个比较严肃的问题关于add标记的合并问题 我们显然每次不能down到儿子那样复杂的会异常的大每次操作接近4n的复杂度比普通的数组模拟复杂度更高。

add标记合并也会带来影响 原本的add比较大现在合并了变小了 可是上一次的add显然更可能更新历史最值这样历史最值还是一个错误的东西。还要考虑怎么再down的时候维护这个标记。我们设定一个下传时候的addmax 再来一个tagmax 钦定下传然后再标准下传我觉得这样很稳复杂度的话 nlogn 好像是多了近乎2/3的常数。

大概是这样的一个毒瘤的东西吧 写完才知道正确与否理性的看是正确的。答案再32位有符号整数内 int 2^31-1好像只有31位靠 非得开long long 增加一倍的大常数 看看能过不能吧...

有点崩溃 代码难度有点高 不知道怎么写 按照上面的思路还有一些细节要处理 啊 我是真的难受 服了这毒瘤线段树 好好的维护什么区间历史最值 真是服了...

又杠了30min 还是感觉 这不是人写的东西 pushdown 和down down里面我觉得还需要下传东西 这样的话下传覆盖儿子也得下传覆盖 故我这思路不太可取还是看看dalao怎么写的吧 我崩溃了200+的代码 总有点纰漏之处。

应该是我对这个标记有点什么误解 被我搞麻烦了 正确的题解是吉如一老师提出的标记合并法解决的。内心无比崩溃。

粘一下 吉老师的题解:

刚接触这一类问题时,这个例题的难度可能较高,所以我们先忽略区间赋值操作。
考虑使用传统的懒标记来解决,首先如果只是询问区间最大值,只面要使用区间加减这一个懒标记(用 Add 表示)就能解决。
现在考虑询问区间历史最大值的最大值。我们定义一种新的懒标记:历史最大的加减标记(用 Pre 表示)。这个标记的定义是:从上一次把这个节点的标记下传的时刻到当前时刻这一时间段中,这个节点中的 Add 标记值到达过的最大值。
现在考虑把第 i 个节点的标记下传到它的儿子 l ,不难发现标记是可以合并的:Prel=max(Prel,Addl+Prei),Addl=Addl+Addi;Prel=max(Prel,Addl+Prei),Addl=Addl+Addi 。至于区间历史最大值信息的更新也与标记的合并类似,只面要将当前的区间最大值加上 Prei 然后与原来的历史最大值进行比较即可。
现在回到原题,我们观察在修改操作过程中,被影响到的节点的变化:如果一个节点没有发生标记下传,那么最开始它一直被区间加减操作所影响,这时我们可以用上面描述的Pre标记来记录,直到某一时刻,这个节点被区间覆盖标记影响A,那么这时这个节点中的所有数都变得完全相同,再之后的所有区间加减修改,对这个节点来说,与区间覆盖操作并没有不同。
因此每一个节点受到的标记可以分成两个部分:第一个部分是区间加减,第二个部分是区间覆盖。因此我们可以用 (x,y) 来表示历史最值标记,它的定义是当前区间在第一阶段时最大的加减标记是 xx ,在第二个阶段时最大的覆盖标记是 yy 。显然这个标记是可以进行合并与更新的。
到此我们就使用最传统的懒标记方法解决了这个问题,时间复杂度 O(mlogn) 。

其实我没怎么看懂 但是这题是 神题经过我的不断思索 决定抄一遍...(学会了一个标记法...

//#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<cctype>
#include<utility>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<deque>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<iomanip>
#include<stack>
#include<string>
#include<cstring>
#define INF 1000000000000ll
#define ll long long
#define db double
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
#define l(p) t[p].l
#define r(p) t[p].r
#define sum(p) t[p].sum
#define mx(p) t[p].mx
#define zz p<<1
#define yy p<<1|1
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getc();}
    while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getc();}
    return x*f;
}
const ll MAXN=100010;
ll n,m;
char ch;
struct wy
{
    ll l,r;
    ll mx;//历史最值
    ll sum;//当前最值
}t[MAXN<<2];
struct data
{
    ll x,y;
    data(ll a=0,ll b=-INF){x=a;y=b;}
    data operator +(const data &a){return data(max(-INF,x+a.x),max(y+a.x,a.y));}
    data operator *(const data &a){return data(max(x,a.x),max(y,a.y));}
}ntag[MAXN<<2],ptag[MAXN<<2];
inline void pushup(ll p)
{
    mx(p)=max(mx(zz),mx(yy));
    sum(p)=max(sum(zz),sum(yy));
}
inline void pushdown(ll p)
{
    ptag[zz]=ptag[zz]*(ntag[zz]+ptag[p]);
    ntag[zz]=ntag[zz]+ntag[p];
    mx(zz)=max(mx(zz),max(sum(zz)+ptag[p].x,ptag[p].y));
    sum(zz)=max(sum(zz)+ntag[p].x,ntag[p].y);
    ptag[yy]=ptag[yy]*(ntag[yy]+ptag[p]);
    ntag[yy]=ntag[yy]+ntag[p];
    mx(yy)=max(mx(yy),max(sum(yy)+ptag[p].x,ptag[p].y));
    sum(yy)=max(sum(yy)+ntag[p].x,ntag[p].y);
    ptag[p]=ntag[p]=data();
}
inline void build(ll p,ll l,ll r)
{
    l(p)=l;r(p)=r;
    if(l==r)
    {
        mx(p)=read();sum(p)=mx(p);
        return;
    }
    ll mid=(l+r)>>1;
    build(zz,l,mid);
    build(yy,mid+1,r);
    pushup(p);
}
inline void change(ll p,ll l,ll r,data a)
{
    if(l<=l(p)&&r>=r(p))
    {
        ntag[p]=ntag[p]+a;
        ptag[p]=ptag[p]*ntag[p];
        sum(p)=max(sum(p)+a.x,a.y);
        mx(p)=max(mx(p),sum(p));
        return;
    }
    ll mid=(l(p)+r(p))>>1;
    pushdown(p);
    if(l<=mid)change(zz,l,r,a);
    if(r>mid)change(yy,l,r,a);
    pushup(p);
}
inline ll ask(ll p,ll l,ll r,ll flag)
{
    if(l<=l(p)&&r>=r(p))return flag?mx(p):sum(p);
    pushdown(p);
    ll mid=(l(p)+r(p))>>1,ans=-INF,w=-INF;
    if(l<=mid)ans=ask(zz,l,r,flag);
    if(r>mid)w=ask(yy,l,r,flag);
    return max(ans,w);
}
signed main()
{
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    n=read();
    build(1,1,n);
    m=read();
    for(ll i=1;i<=m;++i)
    {
        ll x,y,z;
        ch=getc();
        while(ch!=‘Q‘&&ch!=‘A‘&&ch!=‘P‘&&ch!=‘C‘)ch=getc();
        x=read();y=read();
        if(ch==‘Q‘)printf("%lld\n",ask(1,x,y,0));
        if(ch==‘A‘)printf("%lld\n",ask(1,x,y,1));
        if(ch==‘P‘)z=read(),change(1,x,y,data(z,-INF));
        if(ch==‘C‘)z=read(),change(1,x,y,data(-INF,z));
    }
    return 0;
}

原文地址:https://www.cnblogs.com/chdy/p/11408881.html

时间: 2024-10-01 06:37:30

CPU监控 线段树裸题的相关文章

HDU 4893 线段树裸题

Wow! Such Sequence! Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 2512    Accepted Submission(s): 751 Problem Description Recently, Doge got a funny birthday present from his new friend, Pro

HDU 4027 Can you answer these queries? 线段树裸题

题意: 给定2个操作 0.把区间的每个数sqrt 2.求和 因为每个数的sqrt次数很少,所以直接更新到底,用个标记表示是否更新完全(即区间内的数字只有0,1就不用再更新了) #include<stdio.h> #include<iostream> #include<algorithm> #include<vector> #include<cmath> #include<queue> #include<set> #incl

POJ 3468 线段树裸题

这些天一直在看线段树,因为临近期末,所以看得断断续续,弄得有些知识点没能理解得很透切,但我也知道不能钻牛角尖,所以配合着刷题来加深理解. 然后,这是线段树裸题,而且是最简单的区间增加与查询,我参考了ACdreamer的模板,在此基础上自己用宏定义来精简了一下代码: 1 #include<cstdio> 2 typedef long long LL; 3 #define root int rt, int l, int r 4 #define lson rt*2, l, mid 5 #define

bzoj 1036 树链剖分+线段树 裸题

HYSBZ - 1036 题意:中文题 思路:树链剖分裸题,线段树写得比较搓,(在线段树上修改节点u的时候应该修改u映射到线段树后的节点序号,这里wa了半年,真的是半年) AC代码: #include "iostream" #include "string.h" #include "stack" #include "queue" #include "string" #include "vector

Bzoj 3050: [Usaco2013 Jan]Seating(线段树裸题,然而区间修改标记下放和讨论Push_up很揪心)

题目链接 题意:开始有一个空白的区间,每次可能进行两个操作:A 将一个长度为p的区间加入一段连续空白的位置 L:一个区间恢复空白:要求出A不能进行的次数. 非常裸的线段树题目,用线段树统计最大的空白区间,每个节点需要记录当前区间的最长空白区间,从左端开始的最长空白区间,从右端开始的最长空白区间.Push_up的时候要讨论下,可以分别取[l,mid]和[mid+1,r]的最大空白区间,也可以用[l,mid]的从右端开始的最长空白区间+[mid+1,r]从左端开始的最大空白区间. 每次A的时候,就查

bzoj3064: Tyvj 1518 CPU监控 线段树

线段树维护两个值四个标记,注意打标记的顺序. #include<bits/stdc++.h> #define N (1<<18) #define M (l+r>>1) #define P (k<<1) #define S (k<<1|1) #define L l,M,P #define R M+1,r,S #define Z int l=1,int r=n,int k=1 using namespace std; int n; typedef i

hdu3966 树链剖分+线段树 裸题

HDU - 3966 题意:给一颗树,3种操作,Q u 查询u节点的权值,I a b c 对a到b的路径上每个点的点权增加c,D a b c 对a b 路径上所有点的点权减少c 思路:树链剖分+线段树,2个问题,第一,如果是先建树再输入点的点权,记录tip(点映射到线段树后的位置),如果先输入点权,再建树,不仅要记录tip还要记录ran(线段树上某个位置上的点对应的树上点的序号,与tip是相互映射):第二,连接起线段树和树链剖分的是get函数,区间操作才需要用到get函数,单点操作直接在线段树上

codeforces 339D Xenia and Bit Operations 线段树裸题

题目链接 题意: 给定n,下面2^n个数. 第一次 把 a1|a2, a3|a4, 如此得到一个 2^(n-1)个数的序列. 再把这个序列 a1^a2, a3^a4 , 得到一个2^(n-2) 个数的序列 再进行 a1|a2, a3|a4 ··· 直到只剩下一个数v, 我们称v是这个2^n 序列的权值. 下面m个询问: 询问格式: p, b 表示 a[p] = b; 再输出此时序列的权值. 思路:因为这个序列一定是2的倍数,所以用线段树直接这样操作即可.push_up时的深度奇偶来判断此时应该用

BZOJ.3064.CPU监控(线段树 历史最值)

题目链接 \(Description\) 有一个长为n的序列Ai,要求支持查询[l,r]的最值.历史最值,区间加/重设 \(Solution\) 线段树,每个点再维护一个历史(从0到现在)最大值.历史(从上次下传标记到现在)最大的set,add标记 PushDown时肯定是先下放历史标记,之后再用当前标记更新 /* 要记得当要PushDown某个点时,last,now的val都是历史的(下传前),所以now.v + last.add就是下传前值+[下传前到现在]一次最大的修改的值 不能只在Set