整道题可以分成两部分来看。
对于仅有一行的部分:
整个矩阵退化为一个序列,整个问题就变成一个序列上的问题。
简化题意:给定一个序列,每次询问在一段区间内取若干个数,其总和是否能够大于一个给定值。
考虑如下做法:
对于每次询问,二分一个权值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;
}
原文:https://www.cnblogs.com/ninedream/p/13045603.html