CF1102F Elongated Matrix(状压dp)

传送门


解题思路

观察到n非常小,所以考虑状压dp。

问题在于如何确定一个没有后效性的状态/状态转移方程。

设dp[i][j]表示当前已经选择了的行的集合为i(一个二进制数),上一个选择的行是第j行的不考虑首行和末行的答案。

首先转移可以通过提前预处理两行之间的代价优化到O(1)。而首行的选择实际上是有后效性的,所以通过枚举首行来消除这种后效性。

于是过程为:枚举首行i-->枚举状态s-->枚举上一行选的行数j-->枚举下一行要选的行数k-->进行转移。

转移方程为:\(dp[step|(1<<k)][k]=\max(dp[step|(1<<k)][k],\min(dp[step][j],d[k][j]))\)

对于每一个首行i来说,答案通过枚举末行j来求:\(ans=\max(ans,\min(dp[m-1][j],c[j][i]))\)

其中c数组也提前预处理,c[j][i]表示以第j行结尾第i行开头对答案的贡献。

这样总复杂度为 \(O(2^nn^3)\)

注意对于每一个起点i,dp数组清空且初始化dp[1<<i][i]为正无穷。

AC代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<algorithm>
using namespace std;
template<class T>inline void read(T &x)
{
    x=0;register char c=getchar();register bool f=0;
    while(!isdigit(c))f^=c=='-',c=getchar();
    while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
    if(f)x=-x;
}
template<class T>inline void print(T x)
{
    if(x<0)putchar('-'),x=-x;
    if(x>9)print(x/10);
    putchar('0'+x%10);
}
const int maxn=10005;
int n,m,a[20][maxn],dp[(1<<16)][17],d[20][20],c[20][20],ans;
int main(){
	ios::sync_with_stdio(false);
	memset(d,0x3f,sizeof(d));
	memset(c,0x3f,sizeof(c));
	cin>>n>>m;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			cin>>a[i][j];
		}
	} 
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			for(int k=0;k<m;k++){
				d[i][j]=min(d[i][j],abs(a[i][k]-a[j][k]));
			}
		}
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			for(int k=0;k<m-1;k++){
				c[i][j]=min(c[i][j],abs(a[i][k]-a[j][k+1]));
			}
		}
	}
	m=1<<n;
	for(int i=0;i<n;i++){
		memset(dp,0,sizeof(dp));
		dp[1<<i][i]=0x3f3f3f3f;
		for(int step=1;step<m;step++){
			if(step&(1<<i))
			for(int j=0;j<n;j++){
				if(step&(1<<j)){
					for(int k=0;k<n;k++){
						if(step&(1<<k)) continue;
						dp[step|(1<<k)][k]=max(dp[step|(1<<k)][k],min(dp[step][j],d[k][j]));
					}
				}
			}
		}
		for(int j=0;j<n;j++){
			ans=max(ans,min(dp[m-1][j],c[j][i]));
		}
	}
	cout<<ans;
    return 0;
}
posted @ 2021-10-10 14:29  尹昱钦  阅读(41)  评论(0编辑  收藏  举报