1. 程式人生 > >SDOI2010 粟粟的書架 lg2468(可持久化,前綴和)

SDOI2010 粟粟的書架 lg2468(可持久化,前綴和)

希望 主席樹 -- 維護 inline sdoi () 前綴 org

題面見https://www.luogu.org/problemnew/show/P2468

然後這道題屬於合二為一題,看一眼數據範圍就能發現

首先我們先考慮50分,二維前綴和維護一下(反正我不記得公式,手推了半天)

tot[i][j][k]表示矩陣(1,1)到(i,j)中數值大等於k的總和

num[i][j][k]表示矩陣(1,1)到(i,j)中數值大等於k的個數

那麽做法也就顯而易見了,二分k的值進行check

最後註意一個小問題,就是有可能一個k值有多個點,而我不需要全選就能滿足條件,這個可以自行理解一下

後百分之五十,一開始口胡了一個一維前綴和的做法,貌似是兩個log,然而我在學可持久化數據結構,不能偷懶

思考了一下,開一棵權值線段樹,把它變成主席樹,根x代表插入了第x個數後的情況

然後建樹,更新都是裸的操作

關於查詢我的想法我寫在了代碼裏,想不通的可以看一下

// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int w=0,f=1;
    char ch=getchar();
    while(ch<0||ch>9){
        if(ch==-) f=-1;
        ch=getchar();
    }
    
while(ch>=0&&ch<=9){ w=(w<<3)+(w<<1)+ch-48; ch=getchar(); } return w*f; } int n,m,q,a[210][210],tot[210][210][1010],num[210][210][1010],ans,cnt,root[5000010],b[5000010]; int get_sum(int x1,int y1,int x2,int y2,int k,int f) { if(f==1)return tot[x2][y2][k]-tot[x2][y1-1
][k]-tot[x1-1][y2][k]+tot[x1-1][y1-1][k]; else return num[x2][y2][k]-num[x2][y1-1][k]-num[x1-1][y2][k]+num[x1-1][y1-1][k]; } inline void work2(){//二維前綴和大力維護,口胡了一下寫在上面了,就不多解釋了,著重看主席樹 int i,j,k,maxx=0; for(i=1;i<=n;i++){ for(j=1;j<=m;j++){ a[i][j]=read();maxx=max(maxx,a[i][j]); } } for(k=0;k<=maxx;k++){ for(i=1;i<=n;i++){ for(j=1;j<=m;j++){ tot[i][j][k]=tot[i-1][j][k]+tot[i][j-1][k]-tot[i-1][j-1][k]+(a[i][j]>=k)*a[i][j]; num[i][j][k]=num[i-1][j][k]+num[i][j-1][k]-num[i-1][j-1][k]+(a[i][j]>=k); } } } while(q--){ int x1,y1,x2,y2,h; x1=read();y1=read();x2=read();y2=read();h=read(); if(get_sum(x1,y1,x2,y2,0,1)<h) puts("Poor QLW"); else{ int l=0,r=maxx+1;ans=-1; while(l<=r){ int mid=(l+r)>>1; if(get_sum(x1,y1,x2,y2,mid,1)>=h){ ans=mid;l=mid+1; } else r=mid-1; } printf("%d\n",get_sum(x1,y1,x2,y2,ans,2)-(get_sum(x1,y1,x2,y2,ans,1)-h)/ans); } } } struct Node{ int ls,rs,sum,size; }st[50000010]; inline int build(int l,int r){ int pos=cnt++; if(l==r) return pos; int mid=(l+r)>>1; st[pos].ls=build(l,mid); st[pos].rs=build(mid+1,r); return pos; }//常規建樹 inline int update(int tim,int l,int r,int x){//tim表示歷史版本,l,r為範圍,x為我當前插入的數 int pos=cnt++; st[pos]=st[tim];st[pos].size++;st[pos].sum+=x;//這個節點的size+1,sum+=x if(l==r) return pos;//到葉子了,大力返回就好 int mid=(l+r)>>1; if(x<=mid) st[pos].ls=update(st[tim].ls,l,mid,x); else st[pos].rs=update(st[tim].rs,mid+1,r,x); return pos; } inline int query(int l,int r,int fir,int sec,int w){//具體解釋見下方 if(l==r) return (w-1)/l+1;//可能不會整除,就這麽處理一下就好了 int mid=(l+r)>>1;int x=st[st[sec].rs].sum-st[st[fir].rs].sum; if(w<=x) return query(mid+1,r,st[fir].rs,st[sec].rs,w); else return st[st[sec].rs].size-st[st[fir].rs].size+query(l,mid,st[fir].ls,st[sec].ls,w-x); } /* 這棵主席樹是基於權值線段樹的,權值的範圍只有1k 主席樹維護了歷史版本的權值線段樹上的size和sum 然後關於建樹和更新都沒什麽新意 查詢這個我一開始不能很好的理解,那麽我現在稍微解釋一下我的思路 首先l,r,fir,sec,w分別表示區間,版本號,還需要多少值 然後大多數題查詢的時候都是向左子樹查一下,比一下大小 這裏查右子樹是因為這是一棵權值線段樹,我們希望盡量少地選點,也就意味著選的數要盡可能大 那麽能選右子樹(也就是值更大的點),當然選大的啊 如果右子樹總和夠,就往右子樹走,不夠的話,算上右子樹,往左子樹走 */ inline void work1() { int maxw=-1e9-7; for(int i=1;i<=m;i++) b[i]=read(),maxw=max(b[i],maxw); root[0]=build(1,maxw+10); for(int i=1;i<=m;i++) root[i]=update(root[i-1],1,maxw,b[i]); for(int i=1;i<=q;i++) { int y1,y2,h;y1=read(),y1=root[read()-1],y2=read(),y2=root[read()],h=read(); if(st[y2].sum-st[y1].sum<h){puts("Poor QLW");continue;} printf("%d\n",query(1,maxw,y1,y2,h)); } } int main(){ n=read();m=read();q=read(); if(n!=1){//合二為一辣雞題 work2(); } else work1(); return 0; }

SDOI2010 粟粟的書架 lg2468(可持久化,前綴和)