雅禮集訓 2017 Day2 水箱
題目描述
給出一個長度為 n 寬度為 1 ,高度無限的水箱,有 n?1 個擋板將其分為 n 個 1 - 1 的小格,然後向每個小格中註水,水如果超過擋板就會溢出到擋板的另一邊,這裏的水是滿足物理定律的(在無擋板阻攔的情況下會向低處流),現在有 m 個條件 (i,y,k) ,表示從左到右數的第 i 個格子中,在高度為 y+0.5 的地方是否有水,k=1 表示有水,k=0 表示沒有水,請求出這 m 個條件最多能同時滿足多少個條件。本題有多組數據。
輸入格式
第一行一個正整數 T ,為數據組數。
第二行兩個正整數 n 、m ,中間用空格隔開。
接下來一行 n?1 個整數,表示從左到右每一塊隔板的高度。
接下來 m 行,每行三個整數 i 、y 、k ,表示一個條件。
輸出格式
共 T 行,每行對應一組數據的答案。
樣例
樣例輸入
2
3 4
3 4
1 3 1
2 1 0
2 2 0
3 3 1
2 2
2
1 2 0
1 2 1
樣例輸出
3
1
數據範圍與提示
對於 20% 的數據,n,m≤16;
對於另外 10% 的數據,只存在指明某處有水的條件;
對於另外 30% 的數據,n,m≤1000;
對於 100% 的數據,n,m≤1e5,T≤5;
看到這道題,首先應該想到最高的隔板把整個區間分割成兩個獨立的部分,所以對於整段的答案,可以是左右兩個部分的獨立的答案(即水沒有淹沒最高的那個隔板),或者先把兩個部分淹沒,讓水沒過最高的隔板,然後在兩側最近的更高的隔板(箱壁可視為無限高的隔板)的高度以下討論答案。
我用的是線段樹維護區間最高擋板位置,用可並堆維護區間所有要求。
初始時,對於每一個單位水槽,把它上方所有要求按高度維護小根堆,合並時就先直接合並它左右兩個部分,再將堆中左右小於兩側隔板高度的要求按照順序提出來,枚舉淹沒高度,用前綴有水條件個數加後綴無水條件個數更新答案,設這個答案最大值為sum。
對於區間[L,R],若L=R,ans(L,R)=sum。
否則,[L,R]一定能從最高的隔板處分為[L,k],[k+1,R],兩個部分,
可以用ans(L,k)+ans(k+1,R)+高於第k和第k+1之間的隔板低於L外側隔板且低於R外側隔板間所有無水條件的個數更新答案,
也可以用所有[L,R]之間低於第k和第k+1之間的隔板的有水條件個數+sum更新答案。
是的有點像分治...
復雜度分析的話,每個隔板可以和左側比它高的第一個隔板和右側比它高的第一個隔板分別形成兩個區間,所以大概有2n個區間吧...然後每個要求在可並堆中共計被訪問一次,刪除一次,所以再加一個mlogm,肯定是能過的。
AC代碼如下:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 200020 #define mid (l+r>>1) using namespace std; int read(){ int nm=0,fh=1;char cw=getchar(); for(;!isdigit(cw);cw=getchar()) if(cw==‘-‘) fh=-fh; for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-‘0‘); return nm*fh; } struct req{int pos,hs,tpe;}p[M]; bool cmp(req i,req j){ if(i.pos==j.pos) return i.hs<j.hs; return i.pos<j.pos; } int n,m,T,tmp,L[M],R[M],c[M<<2],t[M],ans,rt[M<<2],f[M<<2]; int tot[M<<2],dst[M<<2],son[M][2],fs[M],gt[M],pre[M],suf[M],tk; void build(int x,int l,int r){ if(l==r){c[x]=l;return;} build(x<<1,l,mid),build(x<<1|1,mid+1,r); if(t[c[x<<1]]>t[c[x<<1|1]]) c[x]=c[x<<1]; else c[x]=c[x<<1|1]; } int top(int x,int l,int r,int lt,int rt){ if(rt<l||r<lt) return lt; if(lt<=l&&r<=rt) return c[x]; int t1=top(x<<1,l,mid,lt,rt),t2=top(x<<1|1,mid+1,r,lt,rt); return t[t1]>t[t2]?t1:t2; } int merge(int x,int y){ if(x*y==0) return x+y; if(p[x].hs>p[y].hs) return merge(y,x); int t1=son[x][0],t2=son[x][1],k=merge(t2,y); son[x][1]=k; if(dst[k]>dst[t1]) swap(k,t1); dst[x]=dst[k]+1,son[x][0]=t1,son[x][1]=k; return x; } int del(int x){ int t1=son[x][0],t2=son[x][1]; son[x][0]=son[x][1]=dst[x]=0; return merge(t1,t2); } void dp(int &x,int l,int r){ x=++tmp,f[x]=tot[x]=0; int cnt=0,rf=min(t[l-1],t[r]),sum=0; if(l==r) rt[x]=fs[l]; else{ int tp=top(1,1,n-1,l,r-1); dp(L[x],l,tp),dp(R[x],tp+1,r); rt[x]=merge(rt[L[x]],rt[R[x]]); tot[x]=tot[L[x]]+tot[R[x]]; } while(rt[x]>0){ if(p[rt[x]].hs>=rf) break; if(p[rt[x]].tpe==1) tot[x]++; gt[++cnt]=rt[x],rt[x]=del(rt[x]); } suf[cnt+1]=0,pre[0]=0; for(int i=1;i<=cnt;i++){ pre[i]=pre[i-1],suf[cnt-i+1]=suf[cnt-i+2]; if(p[gt[i]].tpe==1) pre[i]++; if(p[gt[cnt-i+1]].tpe==0) suf[cnt-i+1]++; } for(int i=0;i<=cnt;i++){ if(p[gt[i]].hs==p[gt[i+1]].hs&&i!=0) continue; sum=max(sum,pre[i]+suf[i+1]); } if(l==r){f[x]=sum;return;} f[x]=max(f[L[x]]+f[R[x]]+suf[1],tot[L[x]]+tot[R[x]]+sum); } int main(){ T=read(); while(T--){ n=read(),m=read(),tmp=ans=tk=0,t[0]=t[n]=2147483646; memset(fs,0,sizeof(fs)); for(int i=1;i<n;i++) t[i]=read(); for(int i=1;i<=m;i++) p[i].pos=read(),p[i].hs=read(),p[i].tpe=read(); build(1,1,n-1),sort(p+1,p+m+1,cmp); for(int i=1;i<=m;i++){fs[p[i].pos]=merge(i,fs[p[i].pos]);} dp(tk,1,n),printf("%d\n",f[tk]); } return 0; }
雅禮集訓 2017 Day2 水箱