洛谷P2258 子矩阵——题解

本文分享了一道关于二维最优化选择的DP题目解答,通过搜索枚举出行的所有选择,预处理列分值和列间分值贡献,实现一维DP求解,最终达到O(C(n,r)*rm^2)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送

表示一开始也是一脸懵逼,虽然想到了DP,但面对多变的状态不知从何转移及怎么合理记录状态。之(借鉴大佬思路)后,豁然开朗,于是在AC后分享一下题解。

发现数据范围出奇地小,不过越是小的数据范围,算法的灵活性就越大。小数据对我们各个算法的组合及时间复杂度的掌握要求很高。面对二维的最优化选择,其实我们可以先通过搜索枚举出行的所有选择,存到一个数组team中,然后在行已经确认的情况下,跑一遍一维的DP:设dp[j][i]为在前j列选择i列的最优情况(为了方便,要求第i选择的列一定是第j列)。则状态转移方程就可写成:dp[j][i]=min(dp[j][i],dp[k][i-1]+lc[j]+hc[k][j]),其中lc为第j列的分值,hc[k][j]为第k列和第j列横向相邻元素对分值的贡献,k=i-1,i-1+1,...,j-1。对于lc和hk

我们可以在每次搜索完成后预处理一下,整个程序的时间复杂度即为O(C(n,r)*rm2),足以解出题。

代码上有一个小优化,详情见注释:

 

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cmath>
 4 
 5 using namespace std;
 6 
 7 int n, m, r, c, num[17][17], ans = 0x7fffffff, team[17], lteam;
 8 int lc[17];     //
 9 int hc[17][17]; //列之间
10 int dp[17][17];
11 
12 void init()
13 {
14     for (int i = 1; i <= m; i++)
15     {
16         lc[i] = 0;
17         for (int j = 1; j < r; j++)
18             lc[i] += abs(num[team[j]][i] - num[team[j + 1]][i]);
19     }
20     for (int i = 1; i < m; i++)
21         for (int j = i + 1; j <= m; j++)
22         {
23             hc[i][j] = 0;
24             for (int k = 1; k <= r; k++)
25                 hc[i][j] += abs(num[team[k]][i] - num[team[k]][j]);
26         }
27 }
28 
29 void DP()
30 {
31     for (int i = 1; i <= m; i++)
32         dp[i][1] = lc[i];
33     if (c == 1)
34     {
35         for (int i = 1; i <= m; i++)
36             ans = ans > dp[i][1] ? dp[i][1] : ans;
37         return;
38     }
39     for (int i = 2; i <= c; i++)
40     {
41         for (int j = i; j <= m - c + i; j++)
42         {
43             dp[j][i] = 0x2fffffff;
44             for (int k = j - 1; k >= i - 1; k--)
45                 dp[j][i] = min(dp[j][i], dp[k][i - 1] + lc[j] + hc[k][j]);
46         }
47     }
48     for (int i = c; i <= m; i++)
49         ans = min(ans, dp[i][c]);
50 }
51 
52 void dfs(int now)
53 {
54     if (now > n)//选择完毕
55     {
56         init();
57         DP();
58         return;
59     }
60     if (r - lteam == n - now + 1)//当剩下的元素与还要选择的元素的数量相等时,必须要选
61     {
62         team[++lteam] = now;
63         dfs(now + 1);
64         --lteam;
65         return;
66     }
67     dfs(now + 1);//当前行要么不选
68     if (lteam < r)//要么在符合条件的情况下选
69     {
70         team[++lteam] = now;
71         dfs(now + 1);
72         --lteam;
73     }
74 }
75 
76 int main()
77 {
78     scanf("%d%d%d%d", &n, &m, &r, &c);
79     for (int i = 1; i <= n; i++)
80         for (int j = 1; j <= m; j++)
81             scanf("%d", &num[i][j]);
82     dfs(1);
83     printf("%d", ans);
84     return 0;
85 }

 

转载于:https://www.cnblogs.com/InductiveSorting-QYF/p/11095230.html

### 子矩阵问题的算法解析 #### 问题描述 给定一个 \( N \times N \) 的矩阵以及两个整数 \( S \) 和 \( K \),目标是判断是否存在一个大小为 \( K \times K \) 的子矩阵,使得其所有元素之和等于 \( S \)[^1]。 为了高效解决此问题,可以采用前缀和的思想来优化时间复杂度。以下是具体的解决方案: --- #### 前缀和的概念 前缀和是一种常见的预处理技术,用于快速计算数组或矩阵中的区间和。对于二维矩阵而言,可以通过构建前缀和矩阵 \( s[i][j] \) 来表示原矩阵中从左上角到位置 \( (i, j) \) 所有元素的累加和[^2]。 具体定义如下: \[ s[i][j] = \text{matrix}[0..i-1][0..j-1] \] 通过前缀和矩阵,任意子矩阵范围内的元素和可以在常数时间内计算得出。假设要查询以 \( (x_1, y_1) \) 为左上角、\( (x_2, y_2) \) 为右下角的子矩阵,则其和可由以下公式得到[^4]: ```python sum = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] ``` --- #### 算法设计 基于上述理论,我们可以分两步解决问题: 1. **构建前缀和矩阵**:遍历整个输入矩阵并填充前缀和矩阵。 2. **滑动窗口查找**:利用固定大小的 \( K \times K \) 滑窗,在前缀和矩阵中逐一验证是否有满足条件的目标子矩阵。 下面是完整的 Java 实现代码: ```java public class SubMatrixSum { public static boolean hasSubMatrixWithSum(int[][] matrix, int S, int K) { if (matrix == null || matrix.length == 0 || K <= 0) return false; int n = matrix.length; // 获取矩阵维度 // 构建前缀和矩阵 int[][] prefixSum = new int[n + 1][n + 1]; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) { prefixSum[i][j] = matrix[i - 1][j - 1] + prefixSum[i - 1][j] + prefixSum[i][j - 1] - prefixSum[i - 1][j - 1]; } } // 使用滑动窗口方法检测是否存在符合条件的子矩阵 for (int i = K; i <= n; ++i) { // 遍历行起点 for (int j = K; j <= n; ++j) { // 遍历列起点 int sum = prefixSum[i][j] - prefixSum[i - K][j] - prefixSum[i][j - K] + prefixSum[i - K][j - K]; if (sum == S) return true; // 如果找到匹配则返回true } } return false; // 若未发现任何匹配项则返回false } public static void main(String[] args) { int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; int S = 45; int K = 3; System.out.println(hasSubMatrixWithSum(matrix, S, K)); // 输出应为true } } ``` --- #### 复杂度分析 - 时间复杂度: 构造前缀和矩阵的时间复杂度为 \( O(N^2) \),而后续滑动窗口扫描同样需耗时 \( O(N^2) \),因此整体复杂度仍保持在 \( O(N^2) \)。 - 空间复杂度: 主要是存储前缀和矩阵的空间开销,即 \( O(N^2) \)。 --- #### 性能改进方向 如果进一步提升效率,还可以考虑结合哈希表记录中间状态或者应用动态规划策略减少重复运算次数。不过这些方案通常会增加额外空间需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值