#P2808. 第3题-最小操作次数
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 1416
            Accepted: 226
            Difficulty: 6
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2025年4月9日-暑期实习
                              
                      
          
 
- 
                        算法标签>树状数组          
 
第3题-最小操作次数
题解
题面描述
给定一个 N×N 的二维矩阵,其中包含 [1,N2] 的互不相同正整数。允许的操作为:
每次选择矩阵中的一个元素,将其与其在顺时针螺旋顺序中的下一个元素交换。
目标是通过若干次操作,使矩阵变为“顺时针螺旋递增”顺序,即按照螺旋遍历时,元素依次为 1,2,3,…,N2。要求求出最小操作次数,并对 1000000007 取模。
思路
- 将矩阵按照顺时针螺旋顺序遍历,转化为长度为 N2 的一维数组 spiral。
 - 最终目标数组为 [1,2,3,…,N2]。
 - 每次操作相当于交换数组中相邻的两个元素,而最少操作次数等于数组排列的逆序数数量。
 - 采用树状数组(Binary Indexed Tree)方法可以在 O(N2log(N2)) 的时间内统计逆序数。
- 对于数组中每个元素 x(取值范围为 [1,N2),遍历时利用树状数组查询在其前面已经出现的数中,大于 x 的个数,即 inversions += (当前已处理个数 - query(x))。
 - 同时更新树状数组中 x 的计数值。
 
 - 最终对逆序数结果取模 1000000007 得到答案。
 
C++
#include <iostream>
#include <vector>
using namespace std;
 
const int MOD = 1000000007;
 
// 树状数组类,用于统计前缀和
struct BIT {
    vector<int> tree;
    int n;
    BIT(int n): n(n), tree(n+1, 0) {}
    
    // 更新树状数组,在位置 idx 加上 val
    void update(int idx, int val) {
        for(; idx <= n; idx += idx & -idx)
            tree[idx] += val;
    }
    
    // 查询前缀和,从 1 查询到 idx
    int query(int idx) {
        int sum = 0;
        for(; idx > 0; idx -= idx & -idx)
            sum += tree[idx];
        return sum;
    }
};
 
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int N;
    cin >> N;
    vector<vector<int>> matrix(N, vector<int>(N));
    for (int i = 0; i < N; i++){
        for (int j = 0; j < N; j++){
            cin >> matrix[i][j];
        }
    }
    
    // 螺旋遍历提取矩阵中的元素
    vector<int> spiral;
    int top = 0, bottom = N - 1;
    int left = 0, right = N - 1;
    while(top <= bottom && left <= right) {
        // 从左到右
        for(int j = left; j <= right; j++){
            spiral.push_back(matrix[top][j]);
        }
        top++;
        if(top > bottom) break;
        // 从上到下
        for(int i = top; i <= bottom; i++){
            spiral.push_back(matrix[i][right]);
        }
        right--;
        if(left > right) break;
        // 从右到左
        for(int j = right; j >= left; j--){
            spiral.push_back(matrix[bottom][j]);
        }
        bottom--;
        if(top > bottom) break;
        // 从下到上
        for(int i = bottom; i >= top; i--){
            spiral.push_back(matrix[i][left]);
        }
        left++;
    }
    
    // 使用树状数组统计逆序数(最少交换次数)
    int size = spiral.size();
    // 元素取值范围为 [1, N*N]
    BIT bit(N * N);
    long long inversions = 0;
    
    // 从左到右遍历,每个元素统计在其之前已插入的个数中大于它的个数
    for (int i = 0; i < size; i++){
        int x = spiral[i];
        // query(x) 得到前面已经插入的 ≤ x 的数量,因此 (i - query(x)) 即为大于 x 的数量
        inversions = (inversions + (i - bit.query(x))) % MOD;
        bit.update(x, 1);
    }
    
    cout << inversions % MOD << "\n";
    return 0;
}
Python
# 树状数组类,用于统计前缀和
class BIT:
    def __init__(self, n):
        self.n = n
        self.tree = [0] * (n + 1)
    
    # 更新树状数组,在位置 idx 加上 val
    def update(self, idx, val):
        while idx <= self.n:
            self.tree[idx] += val
            idx += idx & -idx
            
    # 查询前缀和,从 1 查询到 idx
    def query(self, idx):
        s = 0
        while idx:
            s += self.tree[idx]
            idx -= idx & -idx
        return s
MOD = 1000000007
def main():
    import sys
    data = sys.stdin.read().strip().split()
    if not data:
        return
    N = int(data[0])
    matrix = []
    index = 1
    for i in range(N):
        row = []
        for j in range(N):
            row.append(int(data[index]))
            index += 1
        matrix.append(row)
    
    # 螺旋遍历提取矩阵中的元素
    spiral = []
    top, bottom = 0, N - 1
    left, right = 0, N - 1
    while top <= bottom and left <= right:
        # 从左到右
        for j in range(left, right + 1):
            spiral.append(matrix[top][j])
        top += 1
        if top > bottom:
            break
        # 从上到下
        for i in range(top, bottom + 1):
            spiral.append(matrix[i][right])
        right -= 1
        if left > right:
            break
        # 从右到左
        for j in range(right, left - 1, -1):
            spiral.append(matrix[bottom][j])
        bottom -= 1
        if top > bottom:
            break
        # 从下到上
        for i in range(bottom, top - 1, -1):
            spiral.append(matrix[i][left])
        left += 1
    
    size = len(spiral)
    bit = BIT(N * N)
    inversions = 0
    # 从左到右遍历,统计当前元素前面大于它的个数
    for i in range(size):
        x = spiral[i]
        inversions = (inversions + (i - bit.query(x))) % MOD
        bit.update(x, 1)
    
    print(inversions % MOD)
if __name__ == "__main__":
    main()
Java
import java.util.*;
import java.io.*;
public class Main {
    static final int MOD = 1000000007;
    
    // 树状数组类,用于统计前缀和
    static class BIT {
        int[] tree;
        int n;
        
        public BIT(int n) {
            this.n = n;
            tree = new int[n + 1];
        }
        
        // 更新树状数组,在位置 idx 加上 val
        public void update(int idx, int val) {
            while(idx <= n) {
                tree[idx] += val;
                idx += idx & -idx;
            }
        }
        
        // 查询前缀和,从 1 到 idx
        public int query(int idx) {
            int sum = 0;
            while(idx > 0) {
                sum += tree[idx];
                idx -= idx & -idx;
            }
            return sum;
        }
    }
    
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine().trim());
        int[][] matrix = new int[N][N];
        for(int i = 0; i < N; i++){
            String[] parts = br.readLine().trim().split("\\s+");
            for(int j = 0; j < N; j++){
                matrix[i][j] = Integer.parseInt(parts[j]);
            }
        }
        
        // 螺旋遍历提取矩阵中的元素
        ArrayList<Integer> spiralList = new ArrayList<>();
        int top = 0, bottom = N - 1;
        int left = 0, right = N - 1;
        while(top <= bottom && left <= right) {
            // 从左到右
            for(int j = left; j <= right; j++){
                spiralList.add(matrix[top][j]);
            }
            top++;
            if(top > bottom) break;
            // 从上到下
            for(int i = top; i <= bottom; i++){
                spiralList.add(matrix[i][right]);
            }
            right--;
            if(left > right) break;
            // 从右到左
            for(int j = right; j >= left; j--){
                spiralList.add(matrix[bottom][j]);
            }
            bottom--;
            if(top > bottom) break;
            // 从下到上
            for(int i = bottom; i >= top; i--){
                spiralList.add(matrix[i][left]);
            }
            left++;
        }
        
        int size = spiralList.size();
        int[] spiral = new int[size];
        for (int i = 0; i < size; i++){
            spiral[i] = spiralList.get(i);
        }
        
        // 使用树状数组统计逆序数(最少操作次数)
        BIT bit = new BIT(N * N);
        long inversions = 0;
        // 从左到右遍历,对于每个元素统计前面大于它的元素个数
        for (int i = 0; i < size; i++){
            int x = spiral[i];
            inversions = (inversions + (i - bit.query(x))) % MOD;
            bit.update(x, 1);
        }
        
        System.out.println(inversions % MOD);
    }
}
        题目内容
给定一个N∗N的二维矩阵,其中包含[1,N2]的互不相同的正整数。
定义一种操作:
每次可以选择矩阵中的一个元素,将其与其在顺时针螺旋顺序中的下一个元素交换位置
例如: 在3∗3的矩阵中,
螺旋顺序为从左上角[0,0]开始,向右到[0,2],向下到[2,2],向左到[2,0],再向上到[1,0],最 后到中心[1,1]。
目标是通过若干次操作,将矩阵变为“顺时针螺旋递增”顺序,即螺旋遍历时元素依次为 1,2,3,...,N2。
求将给定矩阵转换为顺时针螺旋递增顺序所需的最小操作次数。
输入描述
第一行为整数N;
接下来N行,每行N个整数,表示矩阵。
参数范围:
矩阵的N取值范围为[1,103];
矩阵中元素的取值范围[1,N2];
输出描述
一个整数; 表示最小操作次数。
特别注意:
结果可能过大,因此结果需要取模1000000007。
例如,计算初始结果为:1000000008,请返回1。
样例1
输入
2
3 1
2 4
输出
3
说明
根据要求,通过若干次操作得到的目标矩阵为:
1 2
4 3
第一次交换,选择(0,0)的3与螺旋顺序上相邻的(0,1)的1交换,矩阵为:
1 3
2 4
第二次交换,选择(1,1)的4与螺旋顺序上相邻的(1,0)的2交换,矩阵为:
1 3
4 2
第三次交换,选择(0,1)的3与螺旋顺序上相邻的(1,1)的2交换,矩阵为:
1 2
4 3
目标达成;
操作次数3;
样例2
输入
3
3 2 1
6 5 4
9 8 7
输出
10
说明
解释:根据要求,通过若干次操作得到的目标矩阵为:
1 2 3
8 9 4
7 6 5
开始交换(注意选择螺旋顺序上相邻的交换);
第1次交换:
3 2 1
5 6 4
9 8 7
第2次交换: 3 2 1
9 6 4
5 8 7
第3次交换: 3 2 1
6 9 4
5 8 7
第4次交换:
3 2 1
6 9 4
8 5 7
第5次交换:
3 2 1
8 9 4
6 5 7
第6次交换:
3 2 1
8 9 4
6 7 5
第7次交换:
3 2 1
8 9 4
7 6 5
第8次交换:
3 1 2
8 9 4
7 6 5
第9次交换:
1 3 2
8 9 4
7 6 5
第10次交换:
1 2 3
8 9 4
7 6 5
目标达成;
操作次数10;