题目大意
对于一个 R R 行 列的矩阵 P P ,有 条询问,每条询问为:给你一个 P P 的子矩阵 ,让你从出 G G 中取出 个数,使得它们的总和小于 Hi H i ,求最小的 s s 。
数据范围
对于 的数据,满足
R,C⩽200,M⩽200000
R
,
C
⩽
200
,
M
⩽
200000
另有
50%
50
%
的数据,满足
R=1,C⩽500000,M⩽20000
R
=
1
,
C
⩽
500000
,
M
⩽
20000
对于
100%
100
%
的数据,满足
1⩽Pi,j⩽1,000,1≤Hi⩽2×109
1
⩽
P
i
,
j
⩽
1
,
000
,
1
≤
H
i
⩽
2
×
10
9
题解
如果你仍然直接看
100%
100
%
的数据范围,那么很抱歉,本题解帮不了你。
因为这题本人只会 分类讨论。
前缀和部分
对于前
50%
50
%
的部分,可以发现
R
R
和 不大。
甚至连
R×C×Pi,j=200×200×1000=40000000
R
×
C
×
P
i
,
j
=
200
×
200
×
1000
=
40000000
都可以承受。
对于二维的问题,一般可以考虑前缀和。
定义:
预处理:
这样就可以迅速求出区间
[{x1,y1}..{x2,y2}]
[
{
x
1
,
y
1
}
.
.
{
x
2
,
y
2
}
]
的
sum
s
u
m
和
num
n
u
m
值。
然后二分答案
s
s
,每次判断区间内大于 的和是否大于等于
Hi
H
i
,注意最后的答案并不一定就是区间内大于等于
s
s
的数量,因为数值为 的数并不一定要取完。
主席树部分
对于后
50%
50
%
的部分,数据只有一行,有关查询区间排名的操作,考虑主席树。
对于原权值线段树,每个节点记录
sum
s
u
m
和
num
n
u
m
,表示在这个范围内的整数的和以及个数。然后对于每个前缀建立一颗这样权值线段树,以主席树的思想建立。
然后对于每个查询操作,直接在主席树中二分答案即可。
代码
为了方便阅读,这里封装成了两个类。
#include<bits/stdc++.h>
using namespace std;
int n,m,q;
#define nc getchar
inline void read(int &sum) {
char ch=nc();
sum=0;
while((ch<'0'||ch>'9')&&(ch!='-'))
ch=nc();
while(ch>='0'&&ch<='9')
sum=sum*10+(ch-48),ch=nc();
}
class line{//后50%
public:
static const int maxn=10000010;
long long sum[maxn],num[maxn];
int L[maxn],R[maxn];//主席树相关
int cnt,T[500010];
line():cnt(0){}
int update(int pre,int x,int l=1,int r=1000){
int rt=++cnt;//新建点
sum[rt]=sum[pre]+x;//继承原点信息
num[rt]=num[pre]+1;
if(l<r){
int mid=(l+r)>>1;
if(x<=mid){
R[rt]=R[pre];
L[rt]=update(L[pre],x,l,mid);
}else{
L[rt]=L[pre];
R[rt]=update(R[pre],x,mid+1,r);
}
}
return rt;
}
int query(int u,int v,int k,int l=1,int r=1000){
int x=sum[R[v]]-sum[R[u]];
//不一定取完
if(l==r) return ceil((double)k/l);
int mid=(l+r)>>1;
if(k>x) return num[R[v]]-num[R[u]]+query(L[u],L[v],k-x,l,mid);
return query(R[u],R[v],k,mid+1,r);
}
int main(void){
for(int i=1,x;i<=m;i++){
read(x);T[i]=update(T[i-1],x);
}
for(int i=1;i<=q;i++){
int x,y,z;
read(x);read(x);read(y),read(y),read(z);
if(sum[T[y]]-sum[T[x-1]]<z) printf("Poor QLW\n");
else printf("%d\n",query(T[x-1],T[y],z));
}
return 0;
}
};
class arr{//前50%
public:
long long sum[210][210][1010];
int num[210][210][1010];
//前缀和相关
long long sums(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]);}
int nums(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]);}
int main(void){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int x;
read(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]+(x>=k?x:0);
num[i][j][k]=num[i-1][j][k]+num[i][j-1][k]-num[i-1][j-1][k]+(x>=k?1:0);
}
}
}
for(int t=1;t<=q;t++){
int x1,x2,y1,y2,k;
read(x1),read(y1),read(x2),read(y2),read(k);
int l=1,r=1000,ans=-1;
while(l<=r){//二分
int mid=(l+r)>>1;
if(sums(x1,y1,x2,y2,mid)>=k)
l=(ans=mid)+1;
else
r=mid-1;
}
if(ans==-1) printf("Poor QLW\n");
else printf("%lld\n",nums(x1,y1,x2,y2,ans)-(sums(x1,y1,x2,y2,ans)-k)/ans);
//不一定取完
}
return 0;
}
};
line *l;
arr *a;
int main(void){
read(n),read(m),read(q);
if(n==1){
l=new line();
l->main();
delete l;
}else{
a=new arr();
a->main();
delete a;
}
return 0;
}
后记
刚开始我是用命名空间分开这两种情况的,但后来发现会MLE
,因为命名空间中的数组不论有没有使用,都会占据空间。因此后来又改成了class,并且根据实际情况申请类的空间(直接在main
中申请会爆栈)。于是就有了上面的代码。