【洛谷P2468】粟粟的书架

题目

题目链接:https://www.luogu.com.cn/problem/P2468
给出一个\(n\times m\)的矩阵,每一个格子有权值,每次询问一个子矩阵中至少需要选出多少个数字才能使得他们的和不小于\(h\)
对于\(50\%\)的数据,满足\(n,m\leq 200\);对于另外\(50\%\)的数据,满足\(n=1,m\leq 500000\)。每个格子的权值不超过\(1000\)

思路

对于\(n,m\leq 200\)的数据,设\(sum[i][j][k]\)表示左上角为\((1,1)\),右下角为\((i,j)\)的矩阵不小于\(k\)的数的和;\(cnt[i][j][k]\)表示左上角为\((1,1)\),右下角为\((i,j)\)的矩阵不小于\(k\)的数的个数。
利用前缀和即可求出\(sum\)\(cnt\)。对于一组询问,就二分选择的数的最小值,判断该矩阵内不小于\(mid\)的数字之和是否不小于\(h\)即可。
时间复杂度\(O(nmk+Q\log k)\),其中\(k\)是权值最大值。

对于\(n=1,m\leq 500000\)的数据,我们建立一棵主席树,类似静态区间第\(k\)小的方法,我们在第\(i\)棵线段树的区间\([l,r]\)记录前\(i\)个数,值域在\([l,r]\)的数字的和以及个数。那么依然和前面的方法一样,二分最小值,然后主席树判断是否达到\(h\)即可。
时间复杂度\(O(n\log k+Q\log k)\)

注意,无论是哪一种方法,我们都要考虑一种情况:假设我们最终二分出来的答案是\(k\),但该矩阵中可能数字\(k\)出现了多次,而我们只需要取几次(不用全部取完)就可以达到\(h\),那么我们设\(s\)为该矩阵不小于\(k\)的数字之和,如果\(s-h\geq k\),那么完全可以少取\((s-h)\bmod k\)个数字\(k\)。这样最终答案就是\(ans-(s-h)\bmod k\)

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=500010,M=210,P=1010;
int n,m,Q,cnt[M][M][P],sum[M][M][P],rt[N];

struct TREE
{
	int tot,lc[N*30],rc[N*30],sum[N*30],cnt[N*30];
	
	void insert(int &x,int now,int l,int r,int k)
	{
		x=++tot;
		lc[x]=lc[now]; rc[x]=rc[now];
		sum[x]=sum[now]+k; cnt[x]=cnt[now]+1;
		if (l==r) return;
		int mid=(l+r)>>1;
		if (k<=mid) insert(lc[x],lc[now],l,mid,k);
			else insert(rc[x],rc[now],mid+1,r,k);
	}
	
	int asksum(int lnow,int rnow,int l,int r,int ql,int qr)
	{
		if (l==ql && r==qr) return sum[rnow]-sum[lnow];
		int mid=(l+r)>>1;
		if (qr<=mid) return asksum(lc[lnow],lc[rnow],l,mid,ql,qr);
		if (ql>mid) return asksum(rc[lnow],rc[rnow],mid+1,r,ql,qr);
		return asksum(lc[lnow],lc[rnow],l,mid,ql,mid)+asksum(rc[lnow],rc[rnow],mid+1,r,mid+1,qr);
	}
	
	int askcnt(int lnow,int rnow,int l,int r,int ql,int qr)
	{
		if (l==ql && r==qr) return cnt[rnow]-cnt[lnow];
		int mid=(l+r)>>1;
		if (qr<=mid) return askcnt(lc[lnow],lc[rnow],l,mid,ql,qr);
		if (ql>mid) return askcnt(rc[lnow],rc[rnow],mid+1,r,ql,qr);
		return askcnt(lc[lnow],lc[rnow],l,mid,ql,mid)+askcnt(rc[lnow],rc[rnow],mid+1,r,mid+1,qr);
	}
}tree;

int main()
{
	scanf("%d%d%d",&n,&m,&Q);
	if (n>1)
	{
		for (int i=1,x;i<=n;i++)
			for (int j=1;j<=m;j++)
			{
				scanf("%d",&x);
				for (int k=1;k<=1000;k++)
				{
					sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]-sum[i-1][j-1][k];
					cnt[i][j][k]=cnt[i-1][j][k]+cnt[i][j-1][k]-cnt[i-1][j-1][k];
					if (x>=k)
					{
						sum[i][j][k]+=x;
						cnt[i][j][k]++;
					}
				}
			} 
		while (Q--)
		{
			int x,y,xx,yy,h,l=1,r=1000,mid;
			scanf("%d%d%d%d%d",&x,&y,&xx,&yy,&h);
			while (l<=r)
			{
				mid=(l+r)>>1;
				if (sum[xx][yy][mid]-sum[xx][y-1][mid]-sum[x-1][yy][mid]+sum[x-1][y-1][mid]>=h) l=mid+1;
					else r=mid-1;
			}
			int ans=cnt[xx][yy][l-1]-cnt[xx][y-1][l-1]-cnt[x-1][yy][l-1]+cnt[x-1][y-1][l-1];
			int s=sum[xx][yy][l-1]-sum[xx][y-1][l-1]-sum[x-1][yy][l-1]+sum[x-1][y-1][l-1];
			if (r) printf("%d\n",ans-((s-h)/(l-1)));
				else printf("Poor QLW\n");
		}
	}
	else
	{
		n=m;
		for (int i=1,x;i<=n;i++)
		{
			scanf("%d",&x);
			tree.insert(rt[i],rt[i-1],1,1000,x);
		}
		while (Q--)
		{
			int WYCtql,l=1,r=1000,ql,qr,mid,h;
			scanf("%d%d%d%d%d",&WYCtql,&ql,&WYCtql,&qr,&h);
			while (l<=r)
			{
				mid=(l+r)>>1;
				if (tree.asksum(rt[ql-1],rt[qr],1,1000,mid,1000)>=h) l=mid+1;
					else r=mid-1;
			}
			if (!r)
			{
				printf("Poor QLW\n");
				continue;
			}
			int ans=tree.askcnt(rt[ql-1],rt[qr],1,1000,l-1,1000);
			int s=tree.asksum(rt[ql-1],rt[qr],1,1000,l-1,1000);
			printf("%d\n",ans-(s-h)/(l-1));
		}
	}
	return 0;
} 
原文地址:https://www.cnblogs.com/stoorz/p/12313499.html