Luogu P2468 [SDOI2010]粟粟的书架

题目大意

对于一个 R R C 列的矩阵 P P ,有 M 条询问,每条询问为:给你一个 P P 的子矩阵 G,让你从出 G G 中取出 s 个数,使得它们的总和小于 Hi H i ,求最小的 s s

数据范围

对于 50% 的数据,满足 R,C200,M200000 R , C ⩽ 200 , M ⩽ 200000
另有 50% 50 % 的数据,满足 R=1,C500000,M20000 R = 1 , C ⩽ 500000 , M ⩽ 20000
对于 100% 100 % 的数据,满足 1Pi,j1,0001Hi2×109 1 ⩽ P i , j ⩽ 1 , 000 , 1 ≤ H i ⩽ 2 × 10 9

题解

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

前缀和部分

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

sumi,j,k=[{1,1}..{i,j}]k s u m i , j , k = 矩 阵 [ { 1 , 1 } . . { i , j } ] 中 , 大 于 等 于 k 的 数 的 和
numi,j,k=[{1,1}..{i,j}]k n u m i , 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<kxk s u m i , j , k = { s u m i − 1 , j , k + s u m i , j − 1 , k − s u m i − 1 , j − 1 , k x < k s u m i − 1 , j , k + s u m i , j − 1 , k − s u m i − 1 , j − 1 , k + x x ⩾ k
numi,j,k={numi1,j,k+numi,j1,knumi1,j1,knumi1,j,k+numi,j1,knumi1,j1,k+1x<kxk n u m i , j , k = { n u m i − 1 , j , k + n u m i , j − 1 , k − n u m i − 1 , j − 1 , k x < k n u m i − 1 , j , k + n u m i , j − 1 , k − n u m i − 1 , j − 1 , k + 1 x ⩾ k

这样就可以迅速求出区间 [{x1,y1}..{x2,y2}] [ { x 1 , y 1 } . . { x 2 , y 2 } ] sum s u m num n u m 值。
然后二分答案 s s ,每次判断区间内大于 s 的和是否大于等于 Hi H i ,注意最后的答案并不一定就是区间内大于等于 s s 的数量,因为数值为 s 的数并不一定要取完。

主席树部分

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

代码

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

#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中申请会爆栈)。于是就有了上面的代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值