分析:
肥肠水的状压DP题。。。
考虑到k非常的小,可以用dp[i][j][l]dp[i][j][l]表示以ii为首的前k个数中,被访问的状态为,且最后一步访问到的数是l+il+i
转移也不难。。但的确是需要点技巧的(所以应该算一道实现题?)
首先直接枚举下一步走哪里(需要判断是否合法),以此来转移jj和
然后考虑转移ii
很显然如果
那么可以直接转移到dp[i+1][j>>1][l−1]dp[i+1][j>>1][l−1]
否则的话,就有两种可能的转移:
1、转移到[i+1,i+k−1][i+1,i+k−1]这个区间中的某个位置
这步直接检查该点是否被访问过,没有的话直接转移即可。
2、转移到[i+k,i+2×k−1][i+k,i+2×k−1]这个区间中的某个位置
这一步需要检查是否从ii开始,到的范围内的位置都被访问过,必须都访问过的条件下,才能转移到dp[i+l′+1][j>>(l′+1)|(1<<(k−1))][k−1]dp[i+l′+1][j>>(l′+1)|(1<<(k−1))][k−1]。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define SF scanf
#define PF printf
#define MAXK 10
#define MAXN 1010
#define MAXH 5010
#define MAXM 260
#define INF 0x3f3f3f3f
using namespace std;
int dp[MAXN][MAXM][MAXK];
int h[MAXN];
int a[MAXN][MAXN];
int n,k;
int main(){
SF("%d%d",&n,&k);
for(int i=1;i<=n;i++)
SF("%d",&h[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
SF("%d",&a[i][j]);
memset(dp,INF,sizeof dp);
dp[1][1][0]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<(1<<k);j++)
for(int l=0;l<k;l++)
if(dp[i][j][l]<INF){
int maxi=-1;
for(int l1=0;l1<k;l1++)
if(j&(1<<l1))
maxi=l1;
for(int l1=0;l1<k;l1++)
if((j&(1<<l1))==0&&h[maxi+i]-h[l1+i]<k&&i+l1<=n+1)
dp[i][j|(1<<l1)][l1]=min(dp[i][j|(1<<l1)][l1],dp[i][j][l]+a[l1+i][i+l]);
if(l!=0&&(j&1)){
dp[i+1][j>>1][l-1]=min(dp[i+1][j>>1][l-1],dp[i][j][l]);
}
else if(l==0){
for(int l1=1;l1<k&&i+l1<=n+1;l1++)
if((j&(1<<l1))==0)
dp[i+1][(j>>1)|(1<<(l1-1))][l1-1]=min(dp[i+1][(j>>1)|(1<<(l1-1))][l1-1],dp[i][j][l]+a[i][i+l1]);
for(int l1=k;l1<2*k&&l1+i<=n+1;l1++){
if((j&(1<<(l1-k)))==0)
break;
dp[i+l1-k+1][(j>>(l1-k+1))|(1<<(k-1))][k-1]=min(dp[i+l1-k+1][(j>>(l1-k+1))|(1<<(k-1))][k-1],dp[i][j][l]+a[i][i+l1]);
}
}
}
PF("%d\n",dp[n+1][1][0]);
}