BZOJ4695 最假女選手(勢能線段樹)
BZOJ題目傳送門
終於體會到初步掌握勢能分析思想的重要性了。
一開始看題,感覺套路還是很一般啊qwq。直接在線段樹上維護最大值和最小值,每次遞歸更新的時候,如果不能完全覆蓋就暴力遞歸下去。挺好寫的欸
鑒於上次寫冒險常數太大的經歷,蒟蒻這次來個碼風奇特的指針線段樹
#include<bits/stdc++.h> #define RG register #define R RG int #define G if(++ip==ie)fread(ip=buf,1,N,stdin) #define pushup s=lc->s+rc->s; mn=min(lc->mn,rc->mn); mx=max(lc->mx,rc->mx) #define pushdn if(ls!=INF)lc->lset(ls),rc->lset(ls),ls=INF; if(la)lc->ladd(la),rc->ladd(la),la=0 using namespace std; typedef long long LL; const int N=1<<20,INF=1e9; char buf[N],*ie=buf+N,*ip=ie-1; int op,x; inline int in(){ G;while(*ip<‘-‘)G; RG bool f=*ip==‘-‘;if(f)G; R x=*ip&15; G;while(*ip>‘-‘){x*=10;x+=*ip&15;G;} return f?-x:x; } struct Node{ Node*lc,*rc; int l,r,m,mn,mx,ls;LL s,la; void build(R b,R e){//建樹 m=((l=b)+(r=e))>>1;ls=INF;la=0; if(b==e){ s=mn=mx=in();return; } (lc=new Node)->build(l,m); (rc=new Node)->build(m+1,r); pushup; } inline void lset(R x){//區間覆蓋 s=(LL)x*(r-l+1);mn=mx=ls=x;la=0; } inline void ladd(R x){//區間加 s+=(LL)x*(r-l+1);mn+=x;mx+=x;la+=x; } void add(R b,R e){//操作1 if(l==b&&r==e)return this->ladd(x); pushdn; if(e<=m)lc->add(b,e); else if(b>m)rc->add(b,e); else lc->add(b,m),rc->add(m+1,e); pushup; } void upd(R b,R e){//操作2/3 if(op&1?mx<=x:mn>=x)return; if(l==b&&r==e&&(op&1?mn>=x:mx<=x))return this->lset(x); pushdn; if(e<=m)lc->upd(b,e); else if(b>m)rc->upd(b,e); else lc->upd(b,m),rc->upd(m+1,e); pushup; } LL qrys(R b,R e){//操作4 if(l==b&&r==e)return s; pushdn; if(e<=m)return lc->qrys(b,e); if(b> m)return rc->qrys(b,e); return lc->qrys(b,m)+rc->qrys(m+1,e); } int qrym(R b,R e){//操作5/6 if(l==b&&r==e)return op&1?mx:mn; pushdn; if(e<=m)return lc->qrym(b,e); if(b> m)return rc->qrym(b,e); if(op&1)return max(lc->qrym(b,m),rc->qrym(m+1,e)); return min(lc->qrym(b,m),rc->qrym(m+1,e)); } }; int main(){ RG Node rt;rt.build(1,in()); for(R m=in(),l,r;m;--m){ op=in();l=in();r=in(); if(op<=3)x=in(),op==1?rt.add(l,r):rt.upd(l,r); else if(op==4)printf("%lld\n",rt.qrys(l,r)); else printf("%d\n",rt.qrym(l,r)); } return 0; }
然後就過了?!然後拿了BZOJ rank1?!
然後這是暴力。
很顯然我們還是要著眼於勢能分析。下面開始瞎逼逼,只討論取min,因為取max是一回事。
隨便定義一個線段樹節點的勢函數為其管轄區間內不同數的個數。這樣初始勢函數總和就是\(O(n\log n)\)級別的。
如果只記區間最小值,那麽顯然如果做一次修改,它可能還是最小值,勢函數並沒有減小。
那要怎麽好呢?記次小值!如果\(x\)大於最小值而小於次小值,那麽我們打上一個區間修改最小值的標記;如果\(x\)大於等於次小值,那麽肯定不同數的個數會減少,此處每額外展開一次暴力遞歸勢函數就會至少減小\(1\),復雜度就是對的了。
註意到這裏要維護和,於是我們在記最小值的時候順便維護區間內最小值的個數。
如果只有區間min/max,那麽復雜度就是一個\(\log\)的了。
可是這裏的操作既有min又有max還有區間加,這時要另外分析。吉老師好像在論文裏說有一個比較松的\(m\log^2\)上界,不過蒟蒻發現還不是很懂勢能分析那一套理論就無法接著逼逼下去了。
cz_xuyixuan隊爺的blog
蒟蒻註意到這樣一句話
代碼實現較長,但理解本做法後編程復雜度並不高。
然而——這就是一個可以拍幾十次次次都WA,重構三次代碼的線段樹嗎?
細節問題:
區間加,區間加最小值,區間加最大值按理來說似乎是獨立的,可是蒟蒻重構了兩邊最後放區間加標記的代碼就是調不出來,無奈之下最先放了區間加,然後又因為玄學問題爆int(本來應該只有區間和要開longlong的啊)
cz_xuyixuan隊爺寫的是先放加min/max再放區間加的。
區間加min的時候,可能還要考慮對區間最大值/次大值的影響(如果區間只有一個或者兩個不同的數的時候)。區間加max同理。
區間加min/max的時候,註意判斷最值的子樹來源後再放。
至於代碼什麽的會讓你痛不欲生。。。蒟蒻硬是用define寫函數把人人要寫4KB+的代碼縮成了3KB。。。此坑慎入!
因為不得已開longlong比
#include<bits/stdc++.h>
#define RG register
#define R RG int
#define I inline void
#define G if(++ip==ie)fread(ip=buf,1,N,stdin)
using namespace std;
typedef long long LL;
const int N=1<<19,INF=1e9;
char buf[N],*ie=buf+N,*ip=ie-1;
int x;
inline int in(){
G;while(*ip<‘-‘)G;
RG bool f=*ip==‘-‘;if(f)G;
R x=*ip&15;
G;while(*ip>‘-‘){x*=10;x+=*ip&15;G;}
return f?-x:x;
}
#define int LL//請無視
struct Node{
Node*lc,*rc;
int l,r,m,mn1,mn2,mnc,mx1,mx2,mxc,lmn,lmx,lad,s;
I up(){//維護最值和次值,小心點寫
s=lc->s+rc->s;
if(lc->mn1<rc->mn1)
mn1=lc->mn1,mnc=lc->mnc,mn2=min(lc->mn2,rc->mn1);
else if(lc->mn1>rc->mn1)
mn1=rc->mn1,mnc=rc->mnc,mn2=min(rc->mn2,lc->mn1);
else mn1=lc->mn1,mnc=lc->mnc+rc->mnc,mn2=min(lc->mn2,rc->mn2);
if(lc->mx1>rc->mx1)
mx1=lc->mx1,mxc=lc->mxc,mx2=max(lc->mx2,rc->mx1);
else if(lc->mx1<rc->mx1)
mx1=rc->mx1,mxc=rc->mxc,mx2=max(rc->mx2,lc->mx1);
else mx1=lc->mx1,mxc=lc->mxc+rc->mxc,mx2=max(lc->mx2,rc->mx2);
}
I dnlad(R x){//區間加
mn1+=x;if(mn2!= INF)mn2+=x;
mx1+=x;if(mx2!=-INF)mx2+=x;
s+=x*(r-l+1);lad+=x;
}
I dnlmn(R x){//區間加最小值
if(mn1==mx1)mx1+=x;if(mn1==mx2)mx2+=x;//註意特判
s+=x*mnc;mn1+=x;lmn+=x;
}
I dnlmx(R x){//區間加最大值
if(mx1==mn1)mn1+=x;if(mx1==mn2)mn2+=x;
s+=x*mxc;mx1+=x;lmx+=x;
}
I dn(){//放標記
if(lad)lc->dnlad(lad),rc->dnlad(lad),lad=0;
if(lmn){
if(lc->mn1<=rc->mn1)lc->dnlmn(lmn);
if(lc->mn1>=rc->mn1)rc->dnlmn(lmn);
lmn=0;
}
if(lmx){
if(lc->mx1>=rc->mx1)lc->dnlmx(lmx);
if(lc->mx1<=rc->mx1)rc->dnlmx(lmx);
lmx=0;
}
}
I build(R b,R e){//建樹
m=((l=b)+(r=e))>>1;lmn=lmx=lad=0;
if(l==r){
s=mn1=mx1=in();mnc=mxc=1;
mn2=INF;mx2=-INF;
return;
}
(lc=new Node)->build(l,m);
(rc=new Node)->build(m+1,r);
this->up();
}
#define sum(x,y) x+y//修改函數模板
#define upd(Fun,Lim,Req,Tag) I Fun(R b,R e){ if(Lim)return; if(l==b&&r==e Req)return this->Tag; this->dn(); if(e<=m)lc->Fun(b,e); else if(b>m)rc->Fun(b,e); else lc->Fun(b,m),rc->Fun(m+1,e); this->up(); }//查詢函數模板
#define qry(Fun,Typ,Ret,Opt) inline Typ Fun(R b,R e){ if(l==b&&r==e)return Ret; this->dn(); if(e<=m)return lc->Fun(b,e); if(b>m)return rc->Fun(b,e); return Opt(lc->Fun(b,m),rc->Fun(m+1,e)); }
upd(us,0,,dnlad(x));
upd(umn,mn1>=x,&&mn2>x,dnlmn(x-mn1));
upd(umx,mx1<=x,&&mx2<x,dnlmx(x-mx1));
qry(qs,LL,s,sum);
qry(qmx,int,mx1,max);
qry(qmn,int,mn1,min);
};
#undef int
int main(){
RG Node rt;rt.build(1,in());
for(R m=in(),op,l,r;m;--m){
op=in();l=in();r=in();
if(op<=3)x=in();
if(op==1)rt.us (l,r);
if(op==2)rt.umn(l,r);
if(op==3)rt.umx(l,r);
if(op==4)printf("%lld\n",rt.qs (l,r));
if(op==5)printf("%lld\n",rt.qmx(l,r));
if(op==6)printf("%lld\n",rt.qmn(l,r));
}
return 0;
}
BZOJ4695 最假女選手(勢能線段樹)