类型:状压
传送门:>Here<
题意:给出$N$个点藏有宝藏,有$M$条可以打通的边(都未打通)。你可以选任意一个点作为起点出发,每一次可以打通一条边来挖目的地的宝藏(不能打通已经挖完宝藏的两个点之间的边),其中打通一条边的费用是$L imes K$,其中$L$表示边的长度,$K$表示从当前点到起点打通的路径经过了几个点。问挖完所有宝藏的最小总费用
解题思路
由于$N leq 12$,考虑状压。其中二进制状态$S$中为$1$的那一位表示已经挖掘
可以枚举起点$i$,设立数组$dp[S]$表示以$i$为起点且到达状态$S$所需要的最小费用,因此答案就是$Min{ dp[2^n-1] }$
然后从起点开始搜索,每一次枚举已经挖掘的所有点(设当前点为$j$,当前状态为$S$),再枚举这一次要打通的点$k$。记录$dis[j]$表示从$j$到起点经过的点的数量,也就是题目中的$K$。则$$dp[S|k] = Min{dp[S] + dis[j]*cost(j ightarrow k)}$$
这样的复杂度并不是$N!$,因为$dp$数组是有条件更新的(有点类似记搜)
Code
注意需要判断$i$到$j$有边
/*By DennyQi 2018.8.17*/ #include <cstdio> #include <queue> #include <cstring> #include <algorithm> #define r read() #define Max(a,b) (((a)>(b)) ? (a) : (b)) #define Min(a,b) (((a)<(b)) ? (a) : (b)) using namespace std; typedef long long ll; const int MAXN = 10010; const int MAXM = 27010; const int INF = 1061109567; inline int read(){ int x = 0; int w = 1; register int c = getchar(); while(c ^ '-' && (c < '0' || c > '9')) c = getchar(); if(c == '-') w = -1, c = getchar(); while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + c - '0', c = getchar();return x * w; } int N,M,x,y,z,ans; int G[15][15],dp[8195],dis[15]; void DFS(int sta){ for(int i = 1; i <= N; ++i){ if(!(sta & (1<<(i-1)))) continue; for(int j = 1; j <= N; ++j){ if(sta & (1<<(j-1))) continue; if(G[i][j] == INF) continue; if(dis[i] * G[i][j] + dp[sta] < dp[sta | (1<<(j-1))]){ dp[sta | (1<<(j-1))] = dis[i] * G[i][j] + dp[sta]; dis[j] = dis[i] + 1; DFS(sta | (1<<(j-1))); } } } } int main(){ N=r,M=r; memset(G, 0x3f, sizeof G); ans = INF; for(int i = 1; i <= M; ++i){ x=r,y=r,z=r; if(z < G[x][y]){ G[x][y] = z; G[y][x] = z; } } for(int i = 1; i <= N; ++i){ memset(dp, 0x3f, sizeof dp); memset(dis, 0, sizeof dis); dis[i] = 1; dp[1<<(i-1)] = 0; DFS(1<<(i-1)); ans = Min(ans, dp[(1<<N) - 1]); } printf("%d", ans); return 0; }