洛谷 子矩阵(暴力枚举,DP)

探讨了一个关于从n*m矩阵中选取r行c列子矩阵的问题,目标是最小化由元素差绝对值构成的成本。文章详细介绍了使用动态规划解决此问题的方法,包括状态定义、转移方程以及实现细节。

题目大意:

有一个n*m矩阵,问我们从中选择r行c列的子矩阵。我们定义cost如下:

相邻的cost:同一行之间相邻的元素做差的绝对值,同一列之间相邻的元素做差的绝对值,

所有相邻的产生的cost加起来即为最后的cost.

注意:(1,2)和(1,3)已经计算了cost,那么(1,3)和(1,2)之间不要重复计算。

问怎么选择子矩阵可以使得cost最小。

n<=16,m<=16

解题思路:

这题首先我们能想到的是利用DP,我们可以把问题简化,假如r=1,那么应该怎么做呢?

我们可以定义状态memo[i][j],其中i表示我们选择第i列,j表示我们总共需要选几列,在这种状态下,我们得到的最小的cost。

for (k = j; k<i;k++)

memo[i][j]=min(memo[k][j-1]+ hc[k][i])

其中hc[a][b],表示相邻的a,b两列的cost。

知道这个之后,我们可以开使枚举多行,并把这个DP转移拓展为多行的情况,其实拓展为多行之后的DP转移和上面差不多。只是,变为

for (k = j; k<i;k++)

memo[i][j]=min(memo[k][j-1]+ hc[k][i]) + lc[i]

其中lc[i]表示,选择这一列之后行之间的cost大小。

废话:

这告诉我们,问题我们需要从简单情况开始下手,不要以为情况弱智就觉得没必要讨论,另外我们也见到了这种暴力加DP的组合。

#include <bits/stdc++.h>
using namespace std;
const int MAXN=17;
int chess[MAXN][MAXN];
int n,m,r,c;
vector<int> row;
int lc[MAXN];
int hc[MAXN][MAXN];
int finans;
const int INF=1e8;
int memo[MAXN][MAXN];
int dp(int chs,int ned){
	if(ned==1)return lc[chs];
	if(memo[chs][ned]!=-1)return memo[chs][ned];
	int ret=INF;
	for(int nc=ned-2;nc<chs;nc++){
		ret= min(dp(nc,ned-1)+hc[nc][chs],ret);
	}
	ret+=lc[chs];
	return memo[chs][ned]=ret;
}
void dfs(int level,int need,int idx){
	if(level==need){
		for(int i=0;i<m;i++){
			int sum=0;
			for(int j=0;j<(int)row.size()-1;j++){
				sum+=abs(chess[row[j]][i]-chess[row[j+1]][i]);
			}
			lc[i]=sum;
		}
		for(int i=0;i<m;i++){
			for(int j=i+1;j<m;j++){
				int sum=0;
			for(int rr=0;rr<(int)row.size();rr++){
				sum+=abs(chess[row[rr]][i] - chess[row[rr]][j]);
			}
			hc[i][j]=hc[j][i]=sum;
			}
		}
		int ans=INF;
		for(int lie=c-1;lie<m;lie++){
		 memset(memo,-1,sizeof(memo));
		 ans= min(ans,dp(lie,c));
		}
		finans=min(finans,ans);
		return ;
	}
	for(int i=idx;i<n;i++){
		row.push_back(i);
		dfs(level+1,need,i+1);
		row.pop_back();
	}
}
int main(){
	cin>>n>>m>>r>>c;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			cin>>chess[i][j];
		}
	}
	finans=INF;
	dfs(0,r,0);
	cout<<finans<<endl;
	return 0;
}

 

### 关于最大子矩阵问题的解决方案 最大子矩阵问题是经典的动态规划问题之一,其目标是从一个给定的矩阵中找出具有最大和的子矩阵。以下是解决该问题的一种常见方法。 #### 方法概述 一种常见的策略是通过降维的方式将二维问题转化为一维问题处理。具体来说,可以通过计算每一列的部分和(即前缀和),从而将二维数组压缩成多个一维数组。对于这些一维数组,可以应用类似于求解最大子段和的方法来得到最优解[^1]。 #### 前缀和的应用 为了高效地计算子矩阵的和,通常采用二维前缀和的技术。设 `prefixSum[x2][y2]` 表示从 `(1, 1)` 到 `(x2, y2)` 的矩形区域内所有元素的总和,则任意子矩阵 `(x1, y1), (x2, y2)` 的和可通过如下公式快速得出: \[ \text{subMatrixSum} = \text{prefixSum}[x2][y2] - \text{prefixSum}[x1-1][y2] - \text{prefixSum}[x2][y1-1] + \text{prefixSum}[x1-1][y1-1] \] 此公式的推导基于二维前缀和递推关系[^3]。 #### 动态规划实现 下面是一个完整的 C++ 实现代码,展示了如何结合前缀和与动态规划技术解决问题: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 105; int matrix[MAXN][MAXN]; int prefixSum[MAXN][MAXN]; // 计算二维前缀和 void computePrefixSum(int n, int m) { for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { prefixSum[i][j] = matrix[i][j] + prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1]; } } } // 获取子矩阵和 int getSubmatrixSum(int x1, int y1, int x2, int y2) { return prefixSum[x2][y2] - prefixSum[x1-1][y2] - prefixSum[x2][y1-1] + prefixSum[x1-1][y1-1]; } int main() { int n, m; cin >> n >> m; // 输入矩阵数据 for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { cin >> matrix[i][j]; } } computePrefixSum(n, m); int maxi = INT_MIN; // 枚举左边界和右边界 for (int l = 1; l <= m; ++l) { for (int r = l; r <= m; ++r) { vector<int> dp(n + 1, 0); for (int i = 1; i <= n; ++i) { dp[i] = max(dp[i-1] + getSubmatrixSum(i, l, i, r), getSubmatrixSum(i, l, i, r)); maxi = max(maxi, dp[i]); } } } cout << maxi << endl; return 0; } ``` 上述代码实现了以下功能: 1. **输入读取**:接收矩阵尺寸及其元素。 2. **前缀和预处理**:加速后续子矩阵和查询操作。 3. **动态规划核心逻辑**枚举每一对列作为左右边界,并在此基础上执行一次最大子段和运算。 这种方法的时间复杂度为 \(O(N^2M)\),其中 \(N\) 是行数,\(M\) 是列数,在合理范围内能够满足性能需求[^5]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值