P3959 宝藏(状压 dp)
目录
Description
一任意一个节点为根,每连接一个新的节点需要花费 \(dep*dis\), \(dis\) 为相邻两节点的距离,求最小花费,根的深度为 \(0\)
State
\(1<=n<=12\)
\(1<=m<=3000\)
\(1<=5*10^5\)
Input
4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 1
Output
4
Solution
如果可以想出 \(dp\) 方程还是很好解决的,\(dp[i][j]\) 表示深度为 \(i\) 时,已经扩展的状态为 \(j\) 的最小花费
那么如果从 \(j\) 所构成的树中扩展另一些节点,那么最小花费需要加上 \(i*|S|\),其中 \(S\) 为扩展这些节点所需要的总距离
扩展结点的部分相当于枚举子集,时间复杂度为 \(O(3^n)\)
总时间复杂度为 \(O(n*3^n)\)
Code
const int S = (1 << 12) + 5;
const int N = 1e5 + 5;
int n, m, k, _;
int a[N];
int dp[15][S];
int road[15][15];
int dis[S][S];
int cal(int now, int nxt)
{
int ans = 0;
for(int i = 1; (1 << i - 1) <= nxt; i ++){
if((nxt & (1 << i - 1))){
int minn = 2e9;
for(int j = 1; (1 << j - 1) <= now; j ++){
if((now & (1 << j - 1))){
minn = min(minn, road[i][j]);
}
}
if(minn == 2e9) return minn;
ans += minn;
}
}
return ans;
}
signed main()
{
// IOS;
while(~ sdd(n, m)){
rep(i, 1, n) rep(j, 1, n) road[i][j] = 2e9;
rep(i, 1, m){
int x = read(), y = read(), w = read();
w = min(w, road[x][y]);
road[x][y] = w;
road[y][x] = w;
}
int all = 1 << n;
rep(i, 0, n) rep(j, 0, all) dp[i][j] = 2e9;
rep(i, 0, all) rep(j, 0, all) dis[i][j] = 2e9;
for(int i = 1; i < all; i <<= 1){
dp[0][i] = 0;
}
for(int i = 0; i < all; i ++){
for(int j = i; j; j = (j - 1) & i){
int k = j ^ i; //j -> k,由 j 扩展出 k
dis[j][k] = cal(j, k);
}
}
for(int i = 1; i <= n; i ++){
for(int s = 0; s < all; s ++){
for(int j = s; j; j = (j - 1) & s){
int k = s ^ j;
if(dis[j][k] == 2e9) continue;
dp[i][s] = min(dp[i][s], dp[i - 1][j] + dis[j][k] * i);
}
}
}
int minn = inf;
for(int i = 1; i <= n; i ++){
minn = min(minn, dp[i][all - 1]);
}
pd(minn);
}
// PAUSE;
return 0;
}