统计子矩阵题解
keep_shining · · 题解
P8783 [蓝桥杯 2022 省 B] 统计子矩阵题解
由于 可亲可敬、美丽善良、孜孜不倦、德才兼备、德高望重、呕心沥血、披星戴月、尽心尽力、兢兢业业、鞠躬尽瘁、恪尽职守的(老师说太虚伪) wflengxuexnong让我们写一篇题解,于是,我写了这篇题解。
wflengxuenong
题目大意:
让你求在一个
思路:
前置芝士:二维前缀和
由于要快速求出矩阵的和来,所以我们要用到二维前缀和,如果您刚学,还不熟悉,先去刷一下这几道题:
一维的:T319882 2023山东入门组-T1-植树,P8218 【深进1.例1】求区间和
二维的:P3397 地毯
如果您已经很熟悉了,那请继续往下看。
那说到了前缀和,大家就很容易想到枚举矩阵的四个端点,然后求出这个矩阵的和,判断是否小于等于
暴力 code :
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int n,m,k;
long long ans;
int a[N][N];
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
int main(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[i][j]=read();
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];//统计前缀和
}
}
for(int i=1;i<=n;i++)//枚举行上的右端点
{
for(int j=1;j<=m;j++)//枚举列上的右端点
{
for(int d=0;h<i;h++)//枚举行上的左端点
{
for(int l=0;l<j;l++)//枚举列上的左端点
{
if(a[i][j]+a[d][l]-a[i][l]-a[d][j]<=k)ans++;//求出这个矩阵的和,判断是否小于等于k
}
}
}
}
cout<<ans;
return 0;
}
这可是得了整整80分!
接下来,我们来想一想如何优化它:
我们仔细看题,发现这道题的算法标签里有双指针!于是我们来考虑怎么利用双指针。
我们还是来枚举,这次我们枚举一行上的两个端点,然后我们让列上的右端点不断扩大,而左端点我们要找到第一个符合的左端点,于是我们写出了以下代码:
while(l<=r&&sum[r][j]-sum[r][i-1]-sum[l-1][j]+sum[l-1][i-1]>k)
{
l++;
}
这样我们就找到了左右端点,于是我们就能AC了!
AC code:
#include<bits/stdc++.h>
using namespace std;
#define fst ios::sync_with_stdio(false);cin.tie();cout.tie();
#define Endl endl
#define itn int
#define For(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a,b) for(int i=b;i>=a;i--)
#define endl '\n'
#define re register
#define swap(a,b) a^=b,b^=a,a^=b
#define bit(x) (x&-x)
#define vi vector<int>
#define dg priority_queue<int>
#define xg priority_queue<int,vector<int>,greater<int> >
const int N=2e5+5;
const int P=1e9+7;
int n,m,k,a[501][501],sum[501][501];
long long ans;
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
void write(long long x)
{
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
int main()
{
n=read();//读入
m=read();
k=read();
For(i,1,n)
{
For(j,1,m)
{
a[i][j]=read();//读入矩阵
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];//求前缀和
}
}
For(i,1,m)//枚举列上的左端点
{
For(j,i,m)//枚举列上的右端点,注意j要从i开始,因为右端点一直是大于左端点的
{
int l=1;//行上的左端点
For(r,1,n)//枚举行上的右端点
{
while(l<=r&&sum[r][j]-sum[r][i-1]-sum[l-1][j]+sum[l-1][i-1]>k)//左端点要直到i,j,l,r形成的矩阵中的和小于等于k
{
l++;
}
ans+=r-l+1;//答案加上以i为列上的左端点,以j为列上的右端点,以r为行上右端点的矩阵的和小于等于k的矩阵的数量
}
}
}
write(ans);
return 0;
}