Luogu P2468 [SDOI2010]粟粟的书架

本文介绍了一种针对特定矩阵查询问题的有效解决方法。通过采用前缀和与主席树技术,实现了高效查询矩阵子集中小于给定阈值的元素数量。适用于不同规模的数据集。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

对于一个 RRC 列的矩阵 PP,有 M 条询问,每条询问为:给你一个 PP 的子矩阵 G,让你从出 GG 中取出 s 个数,使得它们的总和小于 HiHi,求最小的 ss

数据范围

对于 50% 的数据,满足R,C200,M200000R,C⩽200,M⩽200000
另有 50%50% 的数据,满足R=1,C500000,M20000R=1,C⩽500000,M⩽20000
对于 100%100% 的数据,满足1Pi,j1,0001Hi2×1091⩽Pi,j⩽1,000,1≤Hi⩽2×109

题解

如果你仍然直接看 100%100% 的数据范围,那么很抱歉,本题解帮不了你。
因为这题本人只会 分类讨论

前缀和部分

对于前 50%50% 的部分,可以发现 RRC 不大。
甚至连R×C×Pi,j=200×200×1000=40000000R×C×Pi,j=200×200×1000=40000000 都可以承受。
对于二维的问题,一般可以考虑前缀和。
定义:

sumi,j,k=[{1,1}..{i,j}]ksumi,j,k=矩阵[{1,1}..{i,j}]中,大于等于k的数的和
numi,j,k=[{1,1}..{i,j}]knumi,j,k=矩阵[{1,1}..{i,j}]中,大于等于k的数的个数

预处理:

sumi,j,k={sumi1,j,k+sumi,j1,ksumi1,j1,ksumi1,j,k+sumi,j1,ksumi1,j1,k+xx<kxksumi,j,k={sumi−1,j,k+sumi,j−1,k−sumi−1,j−1,kx<ksumi−1,j,k+sumi,j−1,k−sumi−1,j−1,k+xx⩾k
numi,j,k={numi1,j,k+numi,j1,knumi1,j1,knumi1,j,k+numi,j1,knumi1,j1,k+1x<kxknumi,j,k={numi−1,j,k+numi,j−1,k−numi−1,j−1,kx<knumi−1,j,k+numi,j−1,k−numi−1,j−1,k+1x⩾k

这样就可以迅速求出区间 [{x1,y1}..{x2,y2}][{x1,y1}..{x2,y2}]sumsumnumnum 值。
然后二分答案 ss,每次判断区间内大于 s 的和是否大于等于 HiHi,注意最后的答案并不一定就是区间内大于等于 ss 的数量,因为数值为 s 的数并不一定要取完。

主席树部分

对于后 50%50% 的部分,数据只有一行,有关查询区间排名的操作,考虑主席树。
对于原权值线段树,每个节点记录 sumsumnumnum ,表示在这个范围内的整数的和以及个数。然后对于每个前缀建立一颗这样权值线段树,以主席树的思想建立。
然后对于每个查询操作,直接在主席树中二分答案即可。

代码

为了方便阅读,这里封装成了两个类。

#include<bits/stdc++.h>
using namespace std;
int n,m,q;
#define nc getchar
inline void read(int &sum) {
    char ch=nc();
    sum=0;
    while((ch<'0'||ch>'9')&&(ch!='-'))
        ch=nc();
    while(ch>='0'&&ch<='9')
        sum=sum*10+(ch-48),ch=nc();
}
class line{//后50%
    public:
    static const int maxn=10000010;
    long long sum[maxn],num[maxn];
    int L[maxn],R[maxn];//主席树相关
    int cnt,T[500010];
    line():cnt(0){}
    int update(int pre,int x,int l=1,int r=1000){
        int rt=++cnt;//新建点
        sum[rt]=sum[pre]+x;//继承原点信息
        num[rt]=num[pre]+1;
        if(l<r){
            int mid=(l+r)>>1;
            if(x<=mid){
                R[rt]=R[pre];
                L[rt]=update(L[pre],x,l,mid);
            }else{
                L[rt]=L[pre];
                R[rt]=update(R[pre],x,mid+1,r);
            }
        }
        return rt;
    }
    int query(int u,int v,int k,int l=1,int r=1000){
        int x=sum[R[v]]-sum[R[u]];
        //不一定取完
        if(l==r) return ceil((double)k/l);
        int mid=(l+r)>>1;
        if(k>x) return num[R[v]]-num[R[u]]+query(L[u],L[v],k-x,l,mid);
        return query(R[u],R[v],k,mid+1,r);
    }
    int main(void){
        for(int i=1,x;i<=m;i++){
            read(x);T[i]=update(T[i-1],x);
        }
        for(int i=1;i<=q;i++){
            int x,y,z;
            read(x);read(x);read(y),read(y),read(z);
            if(sum[T[y]]-sum[T[x-1]]<z) printf("Poor QLW\n");
            else printf("%d\n",query(T[x-1],T[y],z));
        }
        return 0;
    }
};
class arr{//前50%
    public:
    long long sum[210][210][1010];
    int num[210][210][1010];
    //前缀和相关
    long long sums(int x1,int y1,int x2,int y2,int k) {return (sum[x2][y2][k]-sum[x1-1][y2][k]-sum[x2][y1-1][k]+sum[x1-1][y1-1][k]);}
    int nums(int x1,int y1,int x2,int y2,int k) {return (num[x2][y2][k]-num[x1-1][y2][k]-num[x2][y1-1][k]+num[x1-1][y1-1][k]);}
    int main(void){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                int x;
                read(x);
                for(int k=1;k<=1000;k++){
                    sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]-sum[i-1][j-1][k]+(x>=k?x:0);
                    num[i][j][k]=num[i-1][j][k]+num[i][j-1][k]-num[i-1][j-1][k]+(x>=k?1:0);
                }
            }
        }
        for(int t=1;t<=q;t++){
            int x1,x2,y1,y2,k;
            read(x1),read(y1),read(x2),read(y2),read(k);
            int l=1,r=1000,ans=-1;
            while(l<=r){//二分
                int mid=(l+r)>>1;
                if(sums(x1,y1,x2,y2,mid)>=k)
                    l=(ans=mid)+1;
                else
                    r=mid-1;
            }
            if(ans==-1) printf("Poor QLW\n");
            else printf("%lld\n",nums(x1,y1,x2,y2,ans)-(sums(x1,y1,x2,y2,ans)-k)/ans);
            //不一定取完
        }
        return 0;
    }
};
line *l;
arr *a;
int main(void){
    read(n),read(m),read(q);
    if(n==1){
        l=new line();
        l->main();
        delete l;
    }else{
        a=new arr();
        a->main();
        delete a;
    }
    return 0;
}

后记

刚开始我是用命名空间分开这两种情况的,但后来发现会MLE,因为命名空间中的数组不论有没有使用,都会占据空间。因此后来又改成了class,并且根据实际情况申请类的空间(直接在main中申请会爆栈)。于是就有了上面的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值