(Luogu) P2258 子矩阵 (搜索+动态规划)

视频讲解戳这里 (bj聚聚讲的可好了)

传送门

解题思路:这题一看和最大子矩阵很类似。但是要比那题更加的复杂,考虑时有诸多细节。思路就是先把行确定下来,这里用一个搜索,然后再去考虑列,考虑列的时候,我们需要预处理我们所选行 的竖直的分值(即题目定义的相邻元素差的绝对值)和横置的分值。需要两个数组,横置 line[i][j]代表 第i列和第j列横向的之差的绝对值之和;竖直col[i],代表第i列竖直之差的绝对值之和。预处理结束之后,就是一个dp了。 dp[i][j] 表示共选择了j列,最后一列是i列的最小值

递推式就是: dp[i][j]=min(dp[i][j],dp[k][j-1]+col[i]+line[k][i]); 丑陋的代码如下

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=20;
int n,m,s,c,ans=inf;
int a[maxn][maxn];
int num=0,choose[maxn];
int dp[maxn][maxn]; //dp[i][j] 选择了j列,最后一列是i列的最小值 
int line[maxn][maxn],col[maxn];
//  行               列 
void init(){
	//预处理竖直的列 
	for(int j=1;j<=m;++j){ 
		col[j]=0;
		for(int i=2;i<=s;++i){
			col[j]+=abs(a[choose[i]][j]-a[choose[i-1]][j]);
		}
	}	
	//预处理行 
	for(int i=2;i<=m;++i){
		for(int j=1;j<i;++j){
			line[j][i]=0;
			for(int k=1;k<=s;++k){
				line[j][i]+=abs(a[choose[k]][i]-a[choose[k]][j]);
			}	
		}
	} 
	memset(dp,0x3f,sizeof(dp));
}
void solve(){ //再行确定的情况下 考虑列 
	init();
	for(int i=1;i<=m;++i){
		dp[i][1]=col[i];
		if(c==1){
			ans=min(ans,dp[i][1]);
		}
	} 
	for(int j=2;j<=c;++j){
		for(int i=j;i<=m && m-i+j>=c;++i){
			for(int k=j-1;k<i;++k){
				dp[i][j]=min(dp[i][j],dp[k][j-1]+col[i]+line[k][i]);
			}
			if(j==c)
				ans=min(ans,dp[i][j]);
		}
	}
}
void dfs(int step){ //搜索所有行的组合 
	if(num==s){
		solve();
	}
	else{
		for(int i=step;num+n-i+1>=s;++i){
			num++;
			choose[num]=i;
			dfs(i+1);
			num--;
		}
	}
}
int main(){
	std::ios::sync_with_stdio(0);
	cin>>n>>m>>s>>c;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			cin>>a[i][j];
		}
	}
	dfs(1);
	cout<<ans<<endl;	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/TDD_Master/article/details/86547027