[SDOI2010]粟粟的书架

原题链接

原题链接

算法介绍

整道题可以分成两部分来看。

对于仅有一行的部分:

  整个矩阵退化为一个序列,整个问题就变成一个序列上的问题。

  简化题意:给定一个序列,每次询问在一段区间内取若干个数,其总和是否能够大于一个给定值。

考虑如下做法:

  对于每次询问,二分一个权值t,判断区间内权值大于等于t的所有数之和是否大于等于h,最终的结果是使满足以下条件的t最大化:区间内权值大于等于t的所有数之和大于等于h。

  因为要求取的数的个数最少,所以显然尽量取大的数更好。

  考虑主席树维护值域区间内所有数的和(记为sum)以及数的个数(记为cnt)。对于每次询问,同时递归查询以root[r]和root[l-1]为根的两棵主席树,先计算两者右子树的sum之差,记为rsum,若rsum>=h,则递归查询右边值大于等于h的结果,否则递归查询左边值大于等于h-rsum的结果。查询左边时,需要将递归的结果加上rcnt(因为相当于已经将右子树中所有的数都算进去了,还不够h,所以还需左子树的h-rsum,所以总个数是左边需要的个数加上rcnt)。

  注意最后的递归边界。此时已经到达边界,说明我们找到了答案,权值为l=r,而权值为r的数可能不止一个,而我们只需要将此时剩余的h填满即可,所以此时返回的结果应是ceil(h/r),ceil为上取整,而ceil(h/r)=floor((h+r-1)/r),所以可以直接写成(h+r-1)/r。

  整个算法时间复杂度O(nlogn),空间复杂度O(nlogn)

对于形态为矩阵的部分:

  对于每次询问给定的矩阵,同样可以考虑二分。二分一个权值t,最终使得满足如下条件的t最大化:矩阵内所有大于等于t的数的和大于等于h。

  对于矩阵内所有大于等于t的数的和,可以考虑预处理二维前缀和,并且在普通二维前缀和的基础上加一维k,表示矩阵中大于等于k的所有数的二维前缀和。即:val[i,j,k]表示从(1,1)到(i,j)这个矩阵中,所有大于等于k的数的前缀和。

  最后的边界处同样要注意细节处理,对于二分的结果t,在所有等于t的数中,我们只需要取一部分,使得这一部分数加上所有大于t的数的和大于等于h即可,而并不一定要取满所有的等于t的数。我们以sum(x1,y1,x2,y2,t)表示矩阵(x1,y1),(x2,y2)中所有大于等于t的数的和,最终我们要将大于等于t的所有数的个数,减去(sum(x1,y1,x2,y2,t)-h)/t,减去的这一部分就是达到h之后还多出来的部分可以去掉的等于t的数的个数。

  由于我们需要知道矩阵中大于等于t的数的个数,所以可以同样预处理一个二维前缀和数组:num[i,j,k],表示(1,1)到(i,j)这个矩阵中,所有大于等于k的数的个数。

  整个算法时间复杂度O(MAX_V*RC+M*log MAX_V),空间复杂度O(MAX_V*RC)

参考代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5e5+10,M=210;
struct Node{
	int l,r;
	int sum;
	int cnt;
	#define l(p) tree[p].l
	#define r(p) tree[p].r
	#define sum(p) tree[p].sum
	#define cnt(p) tree[p].cnt
}tree[N*22];int idx;
int root[N];
int R,C,m;
int w[N];
int a[M][M];
int val[M][M][1010];
int num[M][M][1010];

int build(int l,int r)
{
	int p=++idx;
	if(l==r)return p;
	int mid=l+r>>1;
	l(p)=build(l,mid);
	r(p)=build(mid+1,r);
	return p;
}

int insert(int now,int l,int r,int pos)
{
	int p=++idx;
	tree[p]=tree[now];
	if(l==r){sum(p)+=pos,cnt(p)+=1;return p;}
	int mid=l+r>>1;
	if(pos<=mid)l(p)=insert(l(now),l,mid,pos);
	else r(p)=insert(r(now),mid+1,r,pos);
	sum(p)=sum(l(p))+sum(r(p));
	cnt(p)=cnt(l(p))+cnt(r(p));
	return p;
} 

int query(int u,int v,int l,int r,int h)
{
	if(l==r)return (h+l-1)/l;
	int rsum=sum(r(v))-sum(r(u));
	int rcnt=cnt(r(v))-cnt(r(u));
	int mid=l+r>>1;
	if(h<=rsum)return query(r(u),r(v),mid+1,r,h);
	else return query(l(u),l(v),l,mid,h-rsum)+rcnt;
}

void solve_segment()
{
	for(int i=1;i<=C;i++)scanf("%d",&w[i]);
	
	root[0]=build(1,1000);
	for(int i=1;i<=C;i++)root[i]=insert(root[i-1],1,1000,w[i]);
	
	while(m--)
	{
		int x1,l,x2,r,h;
		scanf("%d%d%d%d%d",&x1,&l,&x2,&r,&h);
		if(sum(root[r])-sum(root[l-1])<h)
		{
			puts("Poor QLW");
			continue;
		}
		int ans=query(root[l-1],root[r],1,1000,h);
		printf("%d\n",ans);
	}
}

int get_sum(int x1,int y1,int x2,int y2,int k)
{
	return val[x2][y2][k]-val[x1-1][y2][k]-val[x2][y1-1][k]+val[x1-1][y1-1][k];
}

int get_cnt(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];
}

void solve()
{
	int maxv=1;
	for(int i=1;i<=R;i++)
		for(int j=1;j<=C;j++)
			scanf("%d",&a[i][j]),maxv=max(maxv,a[i][j]);
	
	for(int k=1;k<=maxv;k++)
		for(int i=1;i<=R;i++)
			for(int j=1;j<=C;j++)
			{
				val[i][j][k]=val[i-1][j][k]+val[i][j-1][k]-val[i-1][j-1][k]+(a[i][j]>=k?a[i][j]:0);
				num[i][j][k]=num[i-1][j][k]+num[i][j-1][k]-num[i-1][j-1][k]+(a[i][j]>=k?1:0);
			} 
			
	while(m--)
	{
		int x1,y1,x2,y2,h;
		scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&h);
		if(get_sum(x1,y1,x2,y2,1)<h)
		{
			puts("Poor QLW");
			continue;
		}
		int l=1,r=maxv;
		while(l<r)
		{
			int mid=(l+r+1)/2;
			if(get_sum(x1,y1,x2,y2,mid)>=h)l=mid;
			else r=mid-1;
		}
		int ans=get_cnt(x1,y1,x2,y2,r);
		ans-=(get_sum(x1,y1,x2,y2,r)-h)/r;
		printf("%d\n",ans);
	}
}

int main()
{
	scanf("%d%d%d",&R,&C,&m);
	if(R==1)solve_segment();
	else solve();
	return 0;
}
posted @ 2020-06-04 19:29  魑吻丶殇之玖梦  阅读(140)  评论(0编辑  收藏  举报