题解 SDOI2010 【栗栗的书架】

\[ Preface \]

看到这题洛谷标签有 主席树 ,还以为是什么二维主席树的玄学做法(雾
\[ Description \]
给出一个 \(R×C\) 的矩阵。

一共 \(m\) 次询问,每次询问给出一个五元组 \((x1,y1,x2,y2,h)\)

求:在矩阵 \((x1,y1,x2,y2)\) 里至少取多少个数,它们的和大于等于 \(h\)

无解输出 Poor QLW
\[ Solution \]
显然是贪心地取数,数取的越大,就可以越早使和大于等于 \(h\)

因此我们都考虑优先选择大的数,然后一步一步往小的数考虑。

所以我们可以把 " 矩阵内大于等于 \(k\) 的数 " 的和以及个数求出来,然后去二分取数的最小值,把最优性问题转化成一个判定性问题。

\(~\)

注意到:

对于50%的数据,满足R, C≤200,M≤200,000;

另有50%的数据,满足R=1,C≤500,000,M≤20,000;

\(~\)

对于 \(type1\) ,可以直接用 \(O(1000RC)\) 的时间预处理(二维前缀和)。

\(~\)

对于 \(type2\) ,发现 \(O(1000RC)\) 的时间预处理会 \(T\) 飞。

观察到 \(R=1\) ,此时实质上这个矩阵是一个序列,解决 " 区间内大于等于 \(k\) 的数 " 的和以及个数正是主席树擅长的,用主席树维护一下即可。
\[ Code \]

#include

#define RI register int

using namespace std;

inline int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-f;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}

const int N1=210,N2=500100,MLOGN=10001000;

const int MaxV=1000;

int n,m,Q;

int val[N1][N1];
int sum[N1][N1][MaxV+100],cnt[N1][N1][MaxV+100];

int GetSum(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 GetCnt(int x1,int y1,int x2,int y2,int k)
{
    return cnt[x2][y2][k]-cnt[x1-1][y2][k]-cnt[x2][y1-1][k]+cnt[x1-1][y1-1][k];
}

void Work1()
{
    for(RI i=1;i<=n;i++)
        for(RI j=1;j<=m;j++)
            val[i][j]=read();

    for(RI k=1;k<=MaxV;k++)
        for(RI i=1;i<=n;i++)
            for(RI j=1;j<=m;j++)
            {
                sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]-sum[i-1][j-1][k]+(val[i][j]>=k?val[i][j]:0);
                cnt[i][j][k]=cnt[i-1][j][k]+cnt[i][j-1][k]-cnt[i-1][j-1][k]+(val[i][j]>=k?1:0);
            }

    while(Q--)
    {
        int x1=read(),y1=read(),x2=read(),y2=read(),h=read();

        if(GetSum(x1,y1,x2,y2,1)1)
        Work1();
    else
        Work2();

    return 0; 
}

\[ Thanks \ for \ watching \]

你可能感兴趣的:(题解 SDOI2010 【栗栗的书架】)