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;
}
posted @ 2021-11-08 17:08  SSZX_loser_lcy  阅读(24)  评论(0编辑  收藏  举报