SDOI2010 粟粟的书架 lg2468(可持久化,前缀和)

题面见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;
}

猜你喜欢

转载自www.cnblogs.com/wenci/p/10158661.html