Luogu P2468 粟粟的书架 【主席树】【矩阵前缀和】【二分】
前言
好吧,本蒟蒻孤陋寡闻没见过这样鬼畜的二合一的题。
分析
题意大家先自己看一下,应该比较好懂,就是求一个小矩阵内元素之和大等于h的最小元素个数。
我们一看到这个题,就很容易想到二分答案来做,但是这道题的数据范围很奇怪!
看这里:
对于 50% 的数据,满足 \(R, C\le200,M\le{2\times10^5}\).
对于 另外 50% 的数据,满足 \(R=1,C\le{5\times{10^5}},M\le{2\times{10^4}}\)
看到第一行,是不是觉得暴力可以冲一发??
再看到第二行,是不是觉得可以用线段树??又一想,好像主席树可以上??
我们再来看需要维护那些量,首先是大等于某数值数在该范围内的个数,其次是大等于某数值的数在该范围内的和。这样看,主席树就大有用处了!!
再来看第一种情况,我们可以用矩阵前缀和来做,再二分答案即可。
总结
对于第一种情况,我们采用矩阵前缀和的方法,\(O(n^3)\)预处理,维护两个数组。
\[cnt[i][j][k]表示(0,0)到(i,j)中大等于k的数的数量
\]
\[sum[i][j][k]表示(0,0)到(i,j)中大等于k的数的总和
\]
再利用矩阵前缀和即可;
对于第二种情况,我们可以用主席树,维护一颗权值主席树,和区间第k小相似;
但是要注意,当右子树的和大于\(h\)时,直接递归右子树,反之\(h-=rsum\),返回加上\(rcnt\);
以上就是思路了,接下来是代码:
点击查看代码
#include<cstdio>
#define in read()
#define R register
int n,m,q;
int sum[210][200][1100],cnt[210][210][1100];
int val[210][210];
inline int read()
{
static char ch;
int res=0;
while((ch=getchar())<'0'||ch>'9');
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9'){
res=res*10+ch-'0';
}
return res;
}
inline int get_sum(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];
}
//矩阵前缀和
inline int get_cnt(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];
}
const int M=1001;
inline void solve1()
{
for(R int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
val[i][j]=in;
}
}
for(R int k=1;k<=M;k++){//预处理
for(R int i=1;i<=n;i++){
for(R int j=1;j<=m;j++){
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);
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);
}
}
}
int x1,y1,x2,y2,h;
for(R int i=1;i<=q;i++){
x1=in,y1=in,x2=in,y2=in,h=in;
//cout<<get_sum(x1,y1,x2,y2,1)<<endl;;
if(get_sum(x1,y1,x2,y2,1)<h){
printf("Poor QLW\n");
continue;
}else{
int l=0,r=M;
int mid;
while(l<r){
mid=(l+r+1)>>1;
if(get_sum(x1,y1,x2,y2,mid)>=h){
l=mid;
}else{
r=mid-1;
}
}
printf("%d\n",get_cnt(x1,y1,x2,y2,l)-(get_sum(x1,y1,x2,y2,l)-h)/l);//注意考虑多个重复元素情况
}
}
}
const int N2=1e7;
struct node{
int ls,rs,cnt,sum;
}t[N2];
const int N=5e5;
int root[N+100],tot;
int a[N+100];
inline int new_(){
tot++;
t[tot].ls=t[tot].rs=t[tot].cnt=t[tot].sum=0;
return tot;
}
void insert(int &p,int now,int l,int r,int delta,int val1,int val2){
p=new_();//新建节点
t[p]=t[now];
t[p].cnt+=val1;
t[p].sum+=val2;
if(l==r)
return;
int mid=l+r>>1;
if(delta<=mid){
insert(t[p].ls,t[now].ls,l,mid,delta,val1,val2);
}else{
insert(t[p].rs,t[now].rs,mid+1,r,delta,val1,val2);
}
return ;
}
int query(int p,int q,int l,int r,int h){
if(l==r){
return (h-1)/l+1;//叶子结点向上取整
}
int mid=(l+r)>>1;
int rcnt=t[t[q].rs].cnt-t[t[p].rs].cnt;
int rsum=t[t[q].rs].sum-t[t[p].rs].sum;
if(h<=rsum){
return query(t[p].rs,t[q].rs,mid+1,r,h);//优先选大的书
}else{
return query(t[p].ls,t[q].ls,l,mid,h-rsum)+rcnt;//不够再去左边选
}
}
inline void solve2(){
for(R int i=1;i<=m;i++){
a[i]=in;
}
for(R int i=1;i<=m;i++){
insert(root[i],root[i-1],1,M,a[i],1,a[i]);
}
int l,r,h,temp;
while(q--){
temp=in,l=in,temp=in,r=in,h=in;
if(t[root[r]].sum-t[root[l-1]].sum<h){
printf("Poor QLW\n");
}else{
printf("%d\n",query(root[l-1],root[r],1,M,h));
}
}
}
int main()
{
n=in,m=in,q=in;
if(n==1){
solve2();//第二种情况
}else{
solve1();//第一种情况
}
return 0;
}