矩阵重塑(其二)
刷新
时间限制: 1.0 秒
空间限制: 512 MiB
相关文件: 题目目录(样例文件)
题目背景
矩阵转置操作是将矩阵的行和列交换的过程。在转置过程中,原矩阵 AA 的元素 aijaij 会移动到转置后的矩阵 ATAT 的 ajiaji 的位置。这意味着 AA 的第 ii 行第 jj 列的元素在 ATAT 中成为了第 jj 行第 ii 列的元素。
例如,有矩阵 AA 如下:
A=[abcdef]A=[adbecf]
它的转置矩阵 ATAT 会是:
AT=[adbecf]AT=abcdef
矩阵转置在线性代数中是一个基本操作,广泛应用于各种数学和工程领域。
题目描述
给定 n×mn×m 的矩阵 MM,试编写程序支持以下查询和操作:
-
重塑操作 pp、qq:将当前矩阵重塑为 p×qp×q 的形状(重塑的具体定义见上一题);
-
转置操作:将当前矩阵转置;
-
元素查询 ii、jj:查询当前矩阵第 ii 行 jj 列的元素(0≤i<n0≤i<n 且 0≤j<m0≤j<m)。
依次给出 tt 个上述查询或操作,计算其中每个查询的结果。
输入格式
从标准输入读入数据。
输入共 n+t+1n+t+1 行。
输入的第一行包含三个正整数 nn、mm 和 tt。
接下来依次输入初始矩阵 MM 的第 00 到第 n−1n−1 行,每行包含 mm 个整数,按列下标从 00 到 m−1m−1 的顺序依次给出。
接下来输入 tt 行,每行包含形如 op a b
的三个整数,依次给出每个查询或操作。具体输入格式如下:
-
重塑操作:
1 p q
-
转置操作:
2 0 0
-
元素查询:
3 i j
输出格式
输出到标准输出。
每个查询操作输出一行,仅包含一个整数表示查询结果。
样例1输入
3 2 3
1 2
3 4
5 6
3 0 1
1 2 3
3 1 2
样例1输出
2
6
样例2输入
3 2 5
1 2
3 4
5 6
3 1 0
2 0 0
3 1 0
1 3 2
3 1 0
样例2输出
3
2
5
初始矩阵: [123456]135246, (1,0)(1,0) 位置元素为 33;
转置后: [135246][123456], (1,0)(1,0) 位置元素为 22;
重塑后: [135246]154326, (1,0)(1,0) 位置元素为 55。
子任务
8080 的测试数据满足:
- t≤100t≤100;
全部的测试数据满足:
-
t≤105t≤105 且其中转置操作的次数不超过 100100;
-
nn、mm 和所有重塑操作中的 pp、qq 均为正整数且 n×m=p×q≤104n×m=p×q≤104;
-
输入矩阵中每个元素的绝对值不超过 10001000。
提示
-
对于 n×mn×m 的矩阵,虽然转置和重塑操作都可以将矩阵形态变为 m×nm×n,但这两种操作通常会导致不同的结果。
-
评测环境仅提供各语言的标准库,特别地,不提供任何线性代数库(如
numpy
、pytorch
等)。
-------------------------------------------------------
分析:这题有点绕。
如果只想拿80%通过,很简单,暴力就可以。
忘记是哪一年的认证了,也用到了矩阵转置,非常兴奋,一顿操作猛如虎。我也注意到了一些可以优化的地方:
1、连续偶数次转置等于没转;
2、因为可以化为一维,所以所有的重置矩阵的一维不变的, 所以连续重置的话只用看最后一次就行了。
按照这个优化思路,一直是80%,非常痛苦。
有兴趣可以看一下最后的版本:
# 定义重塑操作函数
def rebuild(M:list , p :int ,q:int):
n = len(M)
m = len(M[0])
# 转化为一维矩阵A
A = []
for i in range(n):
for j in range(m):
A.append( M[i][j])
# 转化为新矩阵M_1
i =0
M_1 = []
while( i<= (len(A) - q)):
j = i+ q-1
M_1.append(list( A[i:j+1] ))
i += q
return M_1
# 定义矩阵转置操作
def T( M:list):
return [ [row[i] for row in M] for i in range(len(M[0])) ]
##########主程序############################
n , m , t = map( int, input().split() )
# 输入原始矩阵
M = []
for _ in range(n):
M.append( list( map(int , input().split() ) ) )
# 分析:
# 矩阵的转置,是一个n复杂度的操作
# 矩阵的重塑:是一个n复杂度的操作,n平均在10^2次
# 重塑和转置操作本身优化不了了
# 因此有两个优化方向:1,减少转置操作次数;2减少重塑操作次数
# 此时要注意到,题目说了t最大可以到10^5,但是转置次数不超过100,所以重置操作最大可以到10^5-100次,让复杂度变成n^3
# 因此优化的重点在减少重塑的次数,应当注意到,如果有连续的重塑操作,所有连续的重塑操作中的一维数组是不变的,
# 从而得到这一些连续的重塑操作只用做最后一次的重塑就可以了
# 如果单单考虑重塑,会发现还是超时,所以再加上转置的考虑,如果转置偶次数,相当于没转,那么就不用算终止重塑了
# 开始输入操作
flag_r = False # 记录重塑是否连续
number_t = 0 # 记录连续的转置次数
pre_deed = [] # 前一次操作
deeds = [] # 记录所有操作
for _ in range(t):
deeds.append( list( map(int , input().split() ) ) )
i = 0
while( i < t ):
if deeds[i][0] == 1: # 重塑操作,首先考虑的因素
flag_r = True
# 记录好本次重塑操作
pre_deed = deeds[i]
i += 1
continue
else: # 重塑操作中断了,开始考虑转置的连续
if deeds[i][0] == 2: # 转置操作
if flag_r == True: # 前一个操作是重塑,开始判断有没有断开重塑操作
while( deeds[i][0] == 2):
number_t += 1
i += 1
# 这里一定要注意while结束时i已经是下一位了
if number_t % 2 != 0: # 基数次转置操作,重塑被中断了
flag_r = False
numbers = 0
# 先重塑
M = rebuild(M, pre_deed[1] , pre_deed[2])
# 再转置
M = T(M)
if deeds[i][0] == 1: # 重塑
falg_r = True
i+=1
continue
else: # 说明deeds[i][0] ==3是查询操作
print(M[ deeds[i][1] ][ deeds[i][2] ] )
i+=1
continue
else: #偶数次转置操作,不需要转置,重塑没有中断
if deeds[i][0] == 1: # “再续前缘”,继续重塑
pre_deed = deeds[i]
i+= 1
numbers = 0
continue
else: # 说明deed[0] == 3,是查询操作
# 先重塑
M = rebuild(M, pre_deed[1] , pre_deed[2])
# 再查询
print(M[ deeds[i][1] ][ deeds[i][2] ] )
else: # 前一个操作不是重塑,直接转置
M = T(M)
i+=1
continue
else: # 说明 deed[0] == 3:
if flag_r == True: # 前一个操作是重塑
M = rebuild(M, pre_deed[1] , pre_deed[2]) # 先重塑
flag_r = False
print(M[ deeds[i][1] ][ deeds[i][2] ] ) # 再查询
else:
print(M[ deeds[i][1] ][ deeds[i][2] ] )
i+=1
经历了顶级折磨,我发现,其实最大的时间复杂度,就是转置和重置操作本身,所以想要真正的优化时间复杂度,就要尽量不做这些操作。
继续从一维的A矩阵出发,发现重置操作其实可以直接跳过(A不会变),这就直接省了绝大部分时间复杂度(重置操作可能特别多10^5-100)。
所以实际需要操作的只有转置(最多200次),这就快很多了。但是难点在于,转置要基于一维的A来进行。具体的思路见代码和注释。
# 分析:
# 矩阵的转置,是一个nm复杂度的操作
# 矩阵的重塑:是一个nm复杂度的操作,nm最大在10^4 ,很大,所以能不能直接跳过呢?
# 矩阵的输出:复杂度为1,不用考虑优化
# 重塑和转置操作本身优化不了
# 如果老老实实的遍历,每个输入都操作,最大会有nmt = nm^2的复杂度
# 因此有两个优化方向:1,减少转置操作次数;2减少重塑操作次数
# 此时要注意到,题目说了t最大可以到10^5,但是转置次数不超过100,所以重置操作最大可以到10^5-100次,让复杂度变成(nm)^2
# 因此优化的重点在减少重塑的次数,应当注意到,如果有连续的重塑操作,所有连续的重塑操作中的一维数组是不变的,
# 因此重塑操作不用管,只用记住最后一次重塑操作的p,q就行了,这个p,q会在转置的时候用到
# 而对于转置操作,如果同样从一维A出发,考虑如何把A 矩阵变成转置矩阵的一维矩阵A_2就行了,复杂度nm
# 查询的话,简单,就是在一维A中查询下标为i*q +j的元素就行了
n , m ,t = map(int ,input().split())
M =[]
for _ in range(n): # 输入矩阵M
M.append( list( map(int ,input().split() ) ))
#把M降成一维A
A = []
for i in range(n):
for j in range(m):
A.append(M[i][j])
#print(A)
# 开始处理操作
i = 0
m_shape = [ n ,m] # 用于记录p,q即最后的矩阵形式的行和列,初始值就是原M的行列
while( i<t ):
deed = list( map(int ,input().split() ) )
if deed[0] == 1: #重塑操作,不用管,记录p,q就行,
m_shape = [deed[1],deed[2]]
i+=1
continue
elif deed[0] == 2: # 转置操作
# 考虑如何将一维的A化为转置后矩阵的一维矩阵A_t
A_t = []
# 原来是m_shape[0] * m_shape[1],转置为m_shape[1],m_shape[0]
m_shape = [m_shape[1],m_shape[0]]
for x in range(m_shape[0]): # p,行数
for y in range(m_shape[1]): # q,列数
#print(A[ x + y*pre_deed[2] ])
A_t.append( A[ x + y*m_shape[0] ]) # y*原来的列数
#print(A_t , len(A_t))
A = A_t
i += 1
else: # 输出操作
print(A[ m_shape[1]*deed[1] + deed[2] ] )
i += 1