如果纯暴力的话时间复杂度位$O(20*20!)$,因为20个点全排列,就是20的阶乘种方案。太高肯定T。
状压DP一个明显的特征,行或列一定是一个大一个小,那么把小的那一维,用一个数转换成二进制数来表示这一维上的状态。
这道题就是很经典很明显就是要用状态压缩动态规划。
首先开始做一道动态规划的题目时一定要先考虑状态转移的情况,然后分析状态转移方程。
那么这道题中对于任意一个点 $j$ 来说,只能是从所有没有走过 $j$ 点的状态转移过来的。这点非常重要。然后考虑转移方程。本题中暴力会T,而问题中的数据范围仅有20 ,所以可以经过状态压缩来求解。用$1<<n$这样一个二进制数表示当前问题的状态。如走过点0,1,4的话当前的状态就是10011,表示走过0,1,4三点,即状态的第n位为1,那么第n点就已经走过了。
那么枚举每一个状态i,并枚举每一个点j。对于点j来说,若状态i的第j位为1,那么当前的状态就可以由所有未经过j点的状态中的任意一点k到达。所以就可以开始转移。还需再判断一下,若当前状态i中k是走过的,那么j就可以由k经过由k走向j的这一条路转移过来。
转移方程:
$f[state][j] = f[state_k][k]+weight[k][j]$,其中state表示当前的状态,state_k表示state去掉j点后k的状态。
#include<bits/stdc++.h>
#define ls (p<<1)
#define rs (p<<1|1)
//#define mid (l+r)/2
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=1e5+7;
const ll mod=1e9+7;
const double EPS=1e-10;//-10次方约等于趋近为0
ll n,m;
ll f[1<<20][20];
ll weight[20][20];
int main()
{
scanf("%lld",&n);
over(i,0,n-1)over(j,0,n-1)
scanf("%lld",&weight[i][j]);
memset(f,0x3f,sizeof f);//要求取最小值所以都初始化为最大值
f[1][0]=0;//起点第0点走过了,最短距离为0
for(int i=1;i<(1<<n);++i)
for(int j=0;j<n;++j)
//必须先判断一下状态i的第 j 位==1(也就是 j 点走过了)
if((i>>j)&1)//因为第j点的状态是由没有走过 j 点的状态转移过来的,所以更新的是走过j点的数组
for(int k=0;k<n;++k)//枚举所有能走到j 点的点
if(((i^(1<<j))>>k)&1)//如果当前的状态i 去掉j点之后的状态,走过k点的话就可以转移了//因为是从k走到j点的嘛
f[i][j]=min(f[i][j],f[i^(1<<j)][k]+weight[k][j]);//把i中的j去掉,必须是从未走过j点的状态转移到走过j点的状态+从k走到j的路程
printf("%lld\n",f[(1<<n)-1][n-1]);
return 0;
}