1、问题描述
给定一个 N×M 的矩阵 A, 请你统计有多少个子矩阵 (最小 1×1, 最大 N×M) 满足子矩阵中所有数的和不超过给定的整数 K ?
输入格式
第一行包含三个整数 ,N,M 和 K.
之后 N 行每行包含 M 个整数, 代表矩阵 A.
输出格式
一个整数代表答案。
样例输入
3 4 10
1 2 3 4
5 6 7 8
9 10 11 12
样例输出
19
样例说明
满足条件的子矩阵一共有 19 , 包含:
大小为 1×11×1 的有 10 个。
大小为 1×21×2 的有 3 个。
大小为 1×31×3 的有 2 个。
大小为 1×41×4 的有 1 个。
大小为 2×12×1 的有 3 个。
评测用例规模与约定
对于 30% 的数据, N,M≤20.
对于 70% 的数据, N,M≤100.
对于 100% 的数据, 1≤N,M≤500;0≤ A i j A_{ij} Aij≤1000;1≤K≤250000000.
运行限制
- 最大运行时间:1s
- 最大运行内存: 256M
2、解题思路
2.1 思路一:二维前缀和(超时)
看到这个矩阵求和的题目,我们要马上想到二维数组的前缀和。
这里对二维数组前缀和做简单介绍,这些都是算法竞赛的必备知识了。
求 s [ i , j ] s[i,j] s[i,j],看下图,
假设此时的矩阵用二维数组a[][]
表示,而前缀和用s[][]
表示,那么前缀和用下面的式子表示:
s [ i ] [ j ] = s [ i ] [ j − 1 ] + s [ i − 1 ] [ j ] − s [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1][j-1]+a[i][j] s[i][j]=s[i][j−1]+s[i−1][j]−s[i−1][j−1]+a[i][j]
还有一种方式是给你左上角坐标和右下角坐标,让你求子矩阵的和,比如下图:
求阴影部分的子矩阵和,可以用如下式子表示:
s [ x 1 ∼ x 2 ] [ y 1 ∼ y 2 ] = s [ x 2 ] [ y 2 ] − s [ x 2 ] [ y 1 − 1 ] − s [ x 1 − 1 ] [ y 2 ] + s [ x 1 − 1 ] [ y 1 − 1 ] s[x1\sim x2][y1 \sim y2]=s[x_2][y_2]-s[x_2][y_1-1]-s[x_1-1][y_2]+s[x_1-1][y_1-1] s[x1∼x2][y1∼y2]=s[x2][y2]−s[x2][y1−1]−s[x1−1][y2]+s[x1−1][y1−1]
知道了上面这些基础知识,我们很容易的想到我们只需要枚举矩阵的左上角坐标和右下角的两个坐标就能计算出子矩阵的和了。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.Arrays;
public class Main {
public static int[][] a;
public static int[][] s;
public static long ans;
public static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
public static void main(String[] args) throws IOException{
int n = nextInt();
int m = nextInt();
int k = nextInt();
a = new int[n+1][m+1];
s = new int[n+1][m+1]; //二维数组前缀和
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
a[i][j]=nextInt();
s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1][j-1]+a[i][j]; //计算前缀和
}
}
for (int i = 1; i <=n; i++) {
for (int j = 1; j <=m ; j++) {
for (int l = i; l <=n; l++) {
for (int o = j; o <=m ; o++) {
if(matrixSum(i,j,l,o)<=k){
ans++;
}
}
}
}
}
System.out.println(ans);
}
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
//求子矩阵的和
public static long matrixSum(int x1,int y1,int x2,int y2){
return s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1];
}
}
经过测试,这种方法只能通过60%的数据。
上面的代码中前两重循环确定左上角坐标,后两重循环确定右下角坐标,然后用我们计算前缀和的公式去算就行。
2.2 思路二:二维前缀和+双指针(AC)
方案二同样需要使用前缀和来实现,不过我们需要优化。
这里关于 x 1 x_1 x1和 x 2 x_2 x2我们仍然使用暴力枚举的方式,我们用L表示 y 1 y_1 y1,R表示 y 2 y_2 y2,由于数组没有辅助,在R指针右移的过程中,L也一定会右移(否则求和会超出k的范围),这样我们将问题转化成了一个一维数组的子数组和 ≤ k \le k ≤k的计数问题。
这里通过枚举右边界来实现,此时矩阵左上角坐标为 ( x 1 , L ) (x_1,L) (x1,L),右下角坐标为 ( x 2 , R ) (x_2,R) (x2,R),如果求和结果大于K,那么L指针右移,统计当前R作为右边界的符合条件的数量为 R − L + 1 R-L+1 R−L+1。(因为如果当前的L和R满足条件,那么L到R这一段区间都是满足条件的)。
文章来源:https://www.toymoban.com/news/detail-452441.html
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
//二维前缀和+双指针
public class Final {
public static int[][] a;
public static int[][] s;
public static long ans;
public static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
public static void main(String[] args) throws IOException{
int n = nextInt();
int m = nextInt();
int k = nextInt();
a = new int[n+1][m+1];
s = new int[n+1][m+1]; //二维数组前缀和
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
a[i][j]=nextInt();
s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1][j-1]+a[i][j]; //计算前缀和
}
}
//左上角坐标(x1,L),右下角坐标(x2,R)
//枚举R为右边界
for (int i = 1; i <=n; i++) { //x1
for (int j = i; j <=n ; j++) {//x2
for (int l=1,r=1; r<=m; r++) {//L表示y1,R表示y2
while(l<=r&&matrixSum(i,l,j,r)>k){
l++; //如果总和大于k,l右移
}
ans+=r-l+1; //统计当前R作为右边界的答案数量
}
}
}
System.out.println(ans);
}
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
//求子矩阵的和
public static long matrixSum(int x1,int y1,int x2,int y2){
return s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1];
}
}
文章来源地址https://www.toymoban.com/news/detail-452441.html
到了这里,关于蓝桥杯-统计子矩阵(前缀和+双指针)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!