P2258 子矩阵(dfs+dp)

题目链接:https://www.luogu.org/problemnew/show/P2258
动态规划:https://blog.csdn.net/weixin_39778570/article/details/87014343
ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443

题目:要求从一个n*m矩阵中选出r行c列组成一个小矩阵,求差值和最小的矩阵的值
差值和 = 每列相邻元素的差的绝对值 + 每行相邻元素的差的绝对值
-------------------------------------------------------------------
n,m最大值为16,如果用两次dfs搜行和列显然一定超时
但是考虑到做一维组合可以使用DP(如杨辉3角),那么我们可以先预处理选择的r行,再对选择列进行dp
选行可以用dfs,然后我们预处理出这里选择的行在每一列的差值和(记one),和不同列的差值和(记two)
dp[i][j] 表示前i个选了j个,第i个选中  (j为阶段,i为状态)
dp[i][j] = min(dp[i][j], dp[k][j-1] + one[i] + two[k][i]);    j-1<=k<=i-1
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,m,r,c,a[18][18];
int choose[18],dp[18][18],one[18],two[18][18];
int ans = 2e9;
void pre(){
	// 任意列 
	fo(i,1,m){
		one[i]=0;
		fo(j,1,r-1){
			one[i] += abs(a[choose[j]][i] - a[choose[j+1]][i]);
		} 
	}
	// 任意两列 
	fo(i,1,m){
		fo(j,1,m){
			two[i][j]=0;
			fo(k,1,r){ // 行位置 
				two[i][j] += abs(a[choose[k]][i] - a[choose[k]][j]); 
			}
		} 
	} 
}
void DP(){
	pre();
	memset(dp,127,sizeof(dp));
	dp[0][0]=0;
	// dp[i][j] 表示前i个选了j个,第i个选中
	fo(i,1,m){
		fo(j,1,i){
			for(int k=j-1; k<=i-1; k++){
				dp[i][j] = min(dp[i][j], dp[k][j-1] + one[i] + two[k][i]);
			}
			if(j==c)ans = min(ans, dp[i][j]);
		}
	} 
}
// 搜行 
void dfs(int need, int now){
	if(need==0){
		DP();
		return;
	}
	if(need>n-now+1||now>n)return;
	dfs(need,now+1);
	choose[need] = now;
	dfs(need-1,now+1);
} 
int main(){
	scanf("%d%d%d%d",&n,&m,&r,&c);
	fo(i,1,n)fo(j,1,m)scanf("%d",&a[i][j]); 
	dfs(r,1);
	printf("%d",ans);
	return 0;
}
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页