原题链接
算法介绍
整道题可以分成两部分来看。
对于仅有一行的部分:
整个矩阵退化为一个序列,整个问题就变成一个序列上的问题。
简化题意:给定一个序列,每次询问在一段区间内取若干个数,其总和是否能够大于一个给定值。
考虑如下做法:
对于每次询问,二分一个权值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 ",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 ",ans); } } int main() { scanf("%d%d%d",&R,&C,&m); if(R==1)solve_segment(); else solve(); return 0; }