• 洛谷P2468 [SDOI2010]粟粟的书架 二维前缀和+二分+主席树


    题目链接

    从数据范围可以看出,该题给出的数据有两种类型:

    1.给一个最大 200 X 200 的矩阵。

    2.给一个最长为 500000 的数列。

    那么我们显然需要对这两种数据类型设计两种算法来分别解决。

    1.对于 200 X 200 的矩阵,因为每本书页数不超过1000,可以用二维前缀和处理sum[i][j][k]记录 (1,1)  (i, j) 这个矩阵中高大于k的书的总高度,num[i][j][k]记录(1,1)  (i, j) 这个矩阵中高大于k的书的数量。然后二分至多能取的书的最小高度,换句话说,就是定一个高度,只取高于或等于这个高度的书就能满足要求,找到这个高度最高是多少,这个高度对应的书的数量,就是最少需要取得书的数量了,注意这个答案需要排除掉多余的书,比如第一组样例的第一个询问:

    二分满足要求的最大高度显然是9,但是发现如果把9选完很明显浪费了,这时就要去掉多余的9,具体看代码。

    2.对于第二种情况,我们发现这基本就是一个裸的主席树,那么直接上主席树就好了,当然也要注意上面提到的情况。

    #include <iostream>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <algorithm>
    using namespace std;
    typedef long long LL;
    int row,col,m;
    
    struct Solve1{
        int w[210][210],x1,y1,x2,y2,hi,sum[210][210][1010],num[210][210][1010];
        
        int numm(int k)
        {
            return num[x2][y2][k]-num[x2][y1-1][k]-num[x1-1][y2][k]+num[x1-1][y1-1][k];
        }
        
        int summ(int k)
        {
            return sum[x2][y2][k]-sum[x2][y1-1][k]-sum[x1-1][y2][k]+sum[x1-1][y1-1][k];
        }
        
        int division()
        {
            int l=0,r=1000,res=-1;  //用一个res=-1判断是否无解 
            while(l<r)
            {
                int mid=(l+r+1)>>1;
                if(summ(mid)>=hi) res=mid,l=mid;
                else r=mid-1;
            }
            return res;
        }
        
        void solve()
        {
            for(int i=1;i<=row;i++)
                for(int j=1;j<=col;j++)
                    scanf("%d",&w[i][j]);
            for(int k=0;k<=1000;k++)
                for(int i=1;i<=row;i++)
                    for(int j=1;j<=col;j++)
                    {
                        sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]-sum[i-1][j-1][k];
                        num[i][j][k]=num[i-1][j][k]+num[i][j-1][k]-num[i-1][j-1][k];
                        if(w[i][j]>=k)
                        sum[i][j][k]+=w[i][j],num[i][j][k]+=1; //使用容斥原理求二维前缀和 
                    }
            for(int i=1;i<=m;i++)
            {
                scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&hi);
                int ans=division();
                if(ans==-1) printf("Poor QLW
    ");
                else printf("%d
    ",numm(ans)-(summ(ans)-hi)/ans); //去掉多余的书 
            }
        }
    }Solve1;
    
    struct Solve2{
        #define mid ((l+r)>>1)
        int root[500010],sum[500010<<5],num[500010<<5],L[500010<<5],R[500010<<5],tot;
        //为主席树开空间时一定要看准,我RE自闭。 
        int build(int l,int r)
        {
            int id=++tot;
            sum[id]=0;num[id]=0;
            if(l>=r) return id;
            L[id]=build(l,mid);
            R[id]=build(mid+1,r);
            return id;
        }
        
        int update(int pre,int l,int r,int h)
        {
            int id=++tot;
            sum[id]=sum[pre]+h;num[id]=num[pre]+1;L[id]=L[pre];R[id]=R[pre];
            if(l==h&&r==h) return id;
            if(h<=mid) L[id]=update(L[pre],l,mid,h);
            else R[id]=update(R[pre],mid+1,r,h);
            return id;
        }
        
        //前面基本都是主席树模板,ask有一点变化 
        int ask(int u,int v,int l,int r,int h)
        {
            if(l>=r) return (h-1)/l+1; //锁定答案,但要排除多余的书 
            int x=sum[R[v]]-sum[R[u]];
            if(x>=h) return ask(R[u],R[v],mid+1,r,h); //因为要选尽可能高的书,所以看右子树能否满足 
            else return num[R[v]]-num[R[u]]+ask(L[u],L[v],l,mid,h-x); //如果右子树不够,由左子树来填补 
        }
        
        void solve()
        {
            root[0]=build(1,1000);
            for(int i=1,h;i<=col;i++)
            {
                scanf("%d",&h);
                root[i]=update(root[i-1],1,1000,h);
            }
            for(int i=1,l,r,temp,hi;i<=m;i++)
            {
                scanf("%d%d%d%d%d",&temp,&l,&temp,&r,&hi);
                if(sum[root[r]]-sum[root[l-1]]<hi) printf("Poor QLW
    ");
                else printf("%d
    ",ask(root[l-1],root[r],1,1000,hi));
            }
        }
    }Solve2;
    
    int main()
    {
        scanf("%d%d%d",&row,&col,&m);
        if(row!=1) Solve1.solve();
        else Solve2.solve();
        return 0;
    }
  • 相关阅读:
    《软件需求十步走》阅读笔记一
    《探索需求》读书笔记三
    2018.9.26 随笔
    2018.9.09 随笔
    日期随笔,目录
    2018.9.03 随笔
    linux signal函数遇到的问题
    关于子线程执行两次的问题
    本科四年的一点经验
    linux 网络编程 3---(io多路复用,tcp并发)
  • 原文地址:https://www.cnblogs.com/BakaCirno/p/11538254.html
Copyright © 2020-2023  润新知