CPU监控 线段树裸题
Posted chdy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
以上是关于CPU监控 线段树裸题的主要内容,如果未能解决你的问题,请参考以下文章
Bzoj 3050: [Usaco2013 Jan]Seating(线段树裸题,然而区间修改标记下放和讨论Push_up很揪心)