[IOI2018]meetings會議——分治+線段樹
題目連結:
還是按照編號都從1開始講。
可以發現對於一個詢問如果確定了開會地址那麼答案只和每個點到開會點區間最大值有關。
而題目又沒有強制線上,我們可以按區間最大值來分治。
我們設對於區間[l,r]的答案是ans(l,r),區間中的最大值位於mid處(即h[mid]是區間最大值)。
那麼顯然答案選定的開會點一定在mid的左側或右側,ans(l,r)=min{ans(l,mid-1)+h[mid]*(r-mid+1),ans(mid+1,r)+h[mid]*(mid-l+1)}。
即如果開會點在mid左邊那麼mid及右邊所有點到開會點的代價都是h[mid],如果開會點在mid右邊那麼mid及左邊所有點到開會點的代價都是h[mid]。
因此,ans(l,r)要由ans(l,mid-1)與ans(mid+1,r)合併而來。
那麼我們可以確定一個大致思路:找出當前區間最大值位置然後分治左右區間,在得出左右區間的答案之後再處理詢問區間最大值是當前區間最大值的詢問,然後再遞歸回上一些層。
區間最大值很好求,用ST表維護一下即可,那麼對於每個區間的ans怎麼維護?
對於左端點為l的區間難道要同時維護出ans(l,l),ans(l,l+1),ans(l,l+2)……嗎?
顯然並不需要,我們發現對於每一次遞迴的區間[l,r],需要用到左端點為l的ans只有ans(l,mid-1)和ans(l,r)。
其中前者對應處理當前層詢問前遞迴合併上來的答案,後者則對應處理完當前層詢問後需要遞迴合併上去的答案。
也就是說對於一個點x,在同一時刻只需要維護它作為一個區間左端點和右端點時這兩個答案。
那麼我們就可以用線段樹來實現,對於線段樹上每個葉子結點維護lm和rm兩個資訊,分別表示這個點是區間左/右端點時的區間答案。
因為我們不知道對於當前遞迴區間[l,r]遞歸回上一層時是在上一層最大值的左邊還是右面,所以要用兩棵線段樹分別維護這兩種情況。
總結一下大體思路:對於當前區間找到區間最大值位置並遞迴左右子區間,回溯時處理當前區間需要處理的詢問,對於每個ans(x,mid-1)(l<=x<=mid-1)更新為ans(x,r);對於每個ans(mid+1,x)(mid+1<=x<=r)更新為ans(l,x)。
因為每個點被當做區間最大值一次且每個詢問被處理一次,所以時間複雜度是O((n+q)logn)。
如果還是不太明白可以看程式碼的具體實現。
#include<set> #include<map> #include<queue> #include<cmath> #include<stack> #include<cstdio> #include<vector> #include<bitset> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; int n,q; int h[750010]; int ql[750010]; int qr[750010]; ll res[750010]; int f[750010][21]; int g[750010][21]; int ln[750010]; vector<int>pos[750010]; struct miku { ll lm[4000010]; ll rm[4000010]; ll k[4000010]; ll b[4000010]; int a[4000010]; void cover(int rt) { a[rt]=1; lm[rt]=rm[rt]=k[rt]=b[rt]=0; } void add(int rt,int l,int r,ll K,ll B) { k[rt]+=K; b[rt]+=B; lm[rt]+=K*l+B; rm[rt]+=K*r+B; } void pushup(int rt) { lm[rt]=lm[rt<<1]; rm[rt]=rm[rt<<1|1]; } void pushdown(int rt,int l,int r) { int mid=(l+r)>>1; if(a[rt]) { a[rt]=0; cover(rt<<1); cover(rt<<1|1); } if(k[rt]||b[rt]) { add(rt<<1,l,mid,k[rt],b[rt]); add(rt<<1|1,mid+1,r,k[rt],b[rt]); k[rt]=b[rt]=0; } } void change(int rt,int l,int r,int L,int R,ll val) { if(L<=l&&r<=R) { add(rt,l,r,0,val); return ; } int mid=(l+r)>>1; pushdown(rt,l,r); if(L<=mid) { change(rt<<1,l,mid,L,R,val); } if(R>mid) { change(rt<<1|1,mid+1,r,L,R,val); } pushup(rt); } void merge(int rt,int l,int r,int L,int R,ll K,ll B) { if(L<=l&&r<=R) { ll LV=K*l+B; ll RV=K*r+B; if(LV>=lm[rt]&&RV>=rm[rt]) { return ; } if(LV<=lm[rt]&&RV<=rm[rt]) { cover(rt); add(rt,l,r,K,B); return ; } } pushdown(rt,l,r); int mid=(l+r)>>1; if(L<=mid) { merge(rt<<1,l,mid,L,R,K,B); } if(R>mid) { merge(rt<<1|1,mid+1,r,L,R,K,B); } pushup(rt); } ll query_left(int rt,int l,int r,int x) { if(l==r) { return lm[rt]; } pushdown(rt,l,r); int mid=(l+r)>>1; if(x<=mid) { return query_left(rt<<1,l,mid,x); } else { return query_left(rt<<1|1,mid+1,r,x); } } ll query_right(int rt,int l,int r,int x) { if(l==r) { return rm[rt]; } pushdown(rt,l,r); int mid=(l+r)>>1; if(x<=mid) { return query_right(rt<<1,l,mid,x); } else { return query_right(rt<<1|1,mid+1,r,x); } } }s,t; int cmp(int l,int r) { int len=ln[r-l+1]; if(f[l][len]>=f[r-(1<<len)+1][len]) { return g[l][len]; } else { return g[r-(1<<len)+1][len]; } } void solve(int l,int r) { if(l>r) { return ; } int mid=cmp(l,r); solve(l,mid-1); solve(mid+1,r); int len=pos[mid].size(); for(int i=0;i<len;i++) { int now=pos[mid][i]; res[now]=1ll*h[mid]*(qr[now]-ql[now]+1); if(ql[now]<mid) { res[now]=min(res[now],s.query_left(1,1,n,ql[now])+1ll*h[mid]*(qr[now]-mid+1)); } if(qr[now]>mid) { res[now]=min(res[now],t.query_right(1,1,n,qr[now])+1ll*h[mid]*(mid-ql[now]+1)); } } ll sx=h[mid]; ll tx=h[mid]; if(l<mid) { tx+=t.query_right(1,1,n,mid-1); } if(r>mid) { sx+=s.query_left(1,1,n,mid+1); } s.change(1,1,n,mid,mid,sx); t.change(1,1,n,mid,mid,tx); if(l<mid) { s.change(1,1,n,l,mid-1,1ll*h[mid]*(r-mid+1)); s.merge(1,1,n,l,mid-1,-1ll*h[mid],sx+1ll*mid*h[mid]); } if(r>mid) { t.change(1,1,n,mid+1,r,1ll*h[mid]*(mid-l+1)); t.merge(1,1,n,mid+1,r,1ll*h[mid],tx-1ll*mid*h[mid]); } } int main() { scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) { scanf("%d",&h[i]); f[i][0]=h[i]; g[i][0]=i; } for(int i=1;i<=q;i++) { scanf("%d%d",&ql[i],&qr[i]); ql[i]++; qr[i]++; } for(int i=2;i<=n;i++) { ln[i]=ln[i>>1]+1; } for(int j=1;j<=20;j++) { for(int i=1;i<=n;i++) { if(i+(1<<j)-1>n) { break; } f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]); if(f[i][j]==f[i][j-1]) { g[i][j]=g[i][j-1]; } else { g[i][j]=g[i+(1<<(j-1))][j-1]; } } } for(int i=1;i<=q;i++) { pos[cmp(ql[i],qr[i])].push_back(i); } solve(1,n); for(int i=1;i<=q;i++) { printf("%lld\n",res[i]); } }