赞
踩
给定一个 n × m (n 行 m 列)的矩阵。
设一个矩阵的价值为其所有数中的最大值和最小值的乘积。求给定矩阵的所有大小为 a × b (a 行 b 列)的子矩阵的价值的和。
答案可能很大,你只需要输出答案对 998244353 取模后的结果。
输入的第一行包含四个整数分别表示 n, m, a, b ,相邻整数之间使用一个空格分隔。
接下来 n 行每行包含 m 个整数,相邻整数之间使用一个空格分隔,表示矩阵中的每个数 Ai, j 。
输出一行包含一个整数表示答案。
复制
2 3 1 2 1 2 3 4 5 6
复制
58
1×2+2×3+4×5+5×6 = 58 。
对于 40% 的评测用例,1 ≤ n, m ≤ 100 ;
对于 70% 的评测用例,1 ≤ n, m ≤ 500 ;
对于所有评测用例,1 ≤ a ≤ n ≤ 1000 1 ≤ b ≤ m ≤ 1000 1 ≤ Ai, j ≤ 109 。
在一个n*m的矩阵中,将所有a*b大小的子矩阵的最大值和最小值相乘,它们的结果之和为问题输出。
刚开始做看样例还以为是子序列乘积之和,看完解答后才发现题看错了。
首先是暴力法,定义i,j遍历该矩阵,通过i,j的移动来找出矩阵所有的子矩阵,每找到一个子矩阵最大值,最小值并将他们相乘,将结果保存,以此类推,将所有子矩阵的结果相加保存并取余得到最终结果。
该算法时间复杂度为O(n*m*a*b),分别是遍历矩阵O(n*m)下遍历其子矩阵O(a*b),该时间复杂度不知道能不能完成40%的样例通过。
暴力算法可不可以进行优化呢?我们可以看到,在寻找子矩阵的极值的时候有很多重复查找过程,我们可以不可以将这些重复的过程进行优化呢?这就可以用到我们的滑动窗口的一个算法,在整个列表l,将一确定范围n的子序列结合在一起为一个窗口,怎么让该窗口滑动起来呢,我们可以像队列那样,在通过 i 的遍历过程中,将末端的值l[i-b]进行剔除,在将前端的值l[i]加进来,这样,我们就可以看到该窗口在进行滑动,该算法可以大大减少我们的重复查找。
对于一维列表如此,那么二位列表呢,如果一个a*b的子矩阵整个整个的进行移动改变窗口的值,我们将a个一维的窗口进行滑动,每出去一段,存入一段,我们就检查这两段是否改变的极值,是则更改极值,该极值为该子矩阵的极值。但是,感觉跟暴力法没什么区别,都将子矩阵进行了遍历,只不过是从一块一块的查找极值,一段一段的查找。
为解决这个问题,我们可以通过类似压缩的方式来减少查找量。我们可以先对每一行b列的子矩阵求极大极小值,将极大极小值分别存储为两个二维列表,例如将下面5*5矩阵:
3 | 5 | 1 | 6 | 1 |
1 | 2 | 4 | 7 | 6 |
9 | 4 | 8 | 4 | 6 |
1 | 3 | 7 | 1 | 3 |
4 | 5 | 8 | 9 | 2 |
要求其3*3的所有子矩阵的极值乘积和,先求一行b列(1*3)的子矩阵极值乘积和,求出两个列表:
5 | 6 | 6 |
4 | 7 | 7 |
9 | 8 | 8 |
7 | 7 | 7 |
8 | 9 | 9 |
1 | 1 | 1 |
1 | 2 | 4 |
4 | 4 | 4 |
1 | 1 | 1 |
4 | 5 | 2 |
求出来后,我们要求a*b的子矩阵的极值,就相当于求maxL(minL)每一列乘以a行,但是这种求法太麻烦了,我们可以将maxL(minL)旋转过去,得到旋转后的表:
5 | 4 | 9 | 7 | 8 |
5 | 7 | 8 | 7 | 9 |
6 | 7 | 8 | 7 | 9 |
1 | 1 | 4 | 1 | 4 |
1 | 2 | 4 | 1 | 5 |
1 | 4 | 4 | 1 | 2 |
可以直接通过两个循环嵌套以a列*1行直接求出a*b的子矩阵(要绕似了,不会说话了)。得到子极值极值表:
9 | 9 | 9 |
8 | 8 | 9 |
8 | 8 | 9 |
1 | 1 | 1 |
1 | 1 | 1 |
1 | 1 | 1 |
最后,将两表一一相乘并求和,得到子矩阵的价值的和。
假设我们有一个数组 nums = [3, 9, 7, 4, 5, 8],并且我们希望找到大小为3的滑动窗口中的最大值。现在让我们来分步执行find_max_number函数:
初始时,空队列my_deque和空列表my_want。
当 i = 0 时:
my_deque: [0] (num[0]=3放入队列中)
my_want: [] (当前窗口未达到大小,无最大值)
当 i = 1 时:
my_deque: [1] (num[1]=9放入队列中,因为9比3大,所以3被移出队列)
my_want: [] (当前窗口未达到大小,无最大值)
当 i = 2 时:
my_deque: [1, 2] (num[2]=7放入队列中,因为7比9小,尾插进去)
my_want: [9] (当前窗口达到大小,最大值为9,加入my_want列表)
当 i = 3 时:
my_deque: [1, 2, 3] (num[3]=4放入队列中,因为4比7小,尾插进去)
my_want: [9, 9] (当前窗口达到大小,最大值为9,加入my_want列表)
当 i = 4 时:
my_deque: [4] (nums[1]=9踢出窗口(i-1>=3),num[4]=5放入队列中,因为5比4大,4被移出队列)
my_want: [9, 9, 7] (当前窗口达到大小,最大值为7,加入my_want列表)
当 i = 5 时:
my_deque: [5] (num[5]=8放入队列中,因为8比4,5大,4,5被移出队列)
my_want: [9, 9, 7, 8] (当前窗口达到大小,最大值为8,加入my_want列表)
最终,my_want的输出为 [9, 9, 7, 8]。
根据蓝桥杯2023年第十四届省赛真题-子矩阵-二维滑动窗口+单调队列-Dotcpp编程社区
- from collections import deque
-
-
- def find_max_number(array,size):
- my_deque = deque()
- my_want = []
- for i in range(len(array)):
- #保证队列第一个为当前滑块最大值
- while my_deque and array[my_deque[-1]] < array[i]:
- my_deque.pop()
- my_deque.append(i)
- #目前滑块把之前的最大值划出去了。
- if my_deque[0] <= i - size:
- my_deque.popleft()
- #i从0到n,如果n或m为3,n为10,那么i在0,1的时候不能构成我们需要的子序列【0】,【0,1】
- #不能成为我们需要的n或m的长度的子序列,必须大于3-1=2的下标时我们的最大值才有意义【0,1,2】。
- #后面依次为【1,2,3】,【2,3,4】。。。【7,8,9】,总计8个。
- if i >= size - 1:
- my_want.append(array[my_deque[0]])
- return my_want
-
-
- def find_min_number(array,size):
- my_deque = deque()
- my_want = []
- for i in range(len(array)):
- while my_deque and array[my_deque[-1]] > array[i]:
- my_deque.pop()
- my_deque.append(i)
- # 目前滑块把之前的最值划出去了
- if my_deque[0] <= i - size:
- my_deque.popleft()
-
- if i >= size - 1:
- my_want.append(array[my_deque[0]])
- return my_want
- mo = 998244353
- ans = 0
- n,m,a,b = map(int,input().split())
- lis = []
- for i in range(n):
- lis.append(list(map(int,input().split())))
-
-
- max_number = []
- min_number = []
- for i in range(len(lis)):
- max_number.append(find_max_number(lis[i],b))
- min_number.append(find_min_number(lis[i],b))
-
- #翻转列表,方便对列找极值
- trans_max = []
- trans_min = []
- for i in range(len(max_number[0])):
- trans = []
- for j in range(len(max_number)):
- trans.append(max_number[j][i])
- trans_max.append(trans)
- for i in range(len(min_number[0])):
- trans = []
- for j in range(len(min_number)):
- trans.append(min_number[j][i])
- trans_min.append(trans)
-
-
- maxlie = []
- minlie = []
- # 按列求二维窗口最大值、最小值
- for i in range(len(trans_max)):
- x = find_max_number(trans_max[i],a)
- y = find_min_number(trans_min[i],a)
- maxlie.append(x)
- minlie.append(y)
- ans = 0
- for i in range(len(maxlie)):
- for j in range(len(maxlie[0])):
- ans += (maxlie[i][j] * minlie[i][j]) % mo
- ans %= mo
- print(ans)
-
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。