luogu_P3959 题解
蒟蒻的第一题状压DP紫题。
题目分析
题目转化为在一个有权图上,求一颗生成树,并选择一个根 \(rt\),使 \(\sum_u w(u, fa_u) \times (dep_u - 1)\) 最小,其中 \(dep_{rt} = 1\)
设 \(f_{i, S}\) 表示进行到深度为 \(i\),已经加入树的集合为 \(S\)。
我们尝试列一下转移方程,\(f_{i, S \cup X} = \min \{ f_{i - 1, X} + (i - 1)g_{X, S} \}\),其中 \(S \cap X = \varnothing\),即 \(S \subseteq X^c\) (这一点非常有用,可以将利用子集枚举将复杂度从 \(O(2^{2n})\) 优化为 \(O(3^n)\))。\(g_{X, S}\) 表示已经加入树的集合为 \(X\),要将 \(S\) 加入的最小花费。
不知道大家设状态时有没有怀疑,这种状态设计好像会将不合法的状态加入,好像会加入不是同一深度的点——
吗?
让我们仔细想想,既然不是同一深度,那肯定是比当前深度更小的位置,但是更优的方案是先加入这些深度更小的点。换言之,这些非法的转移答案只会更劣!!!所以我们完全不需要再开一个状态,我们放着不管即可。
接下来考虑如何计算 \(g_{X, Y}\)。注意为了与上面统一,这里的 \(X, Y\) 也满足 \(Y \subseteq X^c\)。那么最简单的方法肯定就是一个一个遍历两个集合中的点计算,比较简单,不多赘述。但是我们是否可以单独计算一个点然后递推?
这时,\(\operatorname{lowbit}\) 出手!因为 \(\operatorname{lowbit}\) 相当于把集合中的最后一个点给拎出来了,那么我们就可以用 \(g_{X, Y - \operatorname{lowbit}(Y)}\) 再加上 \(\operatorname{lowbit}(Y)\) 所代表的点的最小代价来得到。这个方法来自 FlashHu 在洛谷题解中的方法,在这仅为整理,向 FlashHu 巨佬表示敬佩!
那么最后的时间复杂度就为 \(O(n3^n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) (x & (-x))
inline int read(){
int x = 0, f = 1;
char c = getchar();
for(; c < '0' || c > '9'; c = getchar())
if(c == '-') f = -1;
for(; c >= '0' && c <= '9'; c = getchar())
x = (x << 3) + (x << 1) + (c ^ 48);
return f * x;
}
inline void write(int x){
if(x == 0) putchar('0');
if(x < 0) putchar('-'), x = -x;
static int sta[40];
int top = 0;
while(x){
sta[++top] = x % 10;
x /= 10;
}
while(top) putchar(sta[top--] + '0');
}
constexpr int INF = 1e15;//注意极值设置不能太大
constexpr int N = 17;
constexpr int M = (1 << 12) + 5;
int mp[N][N], LOG2[M], pre[M], f[N][M], g[M][M];
int n, m, U;
int ans = INF;
void init(){
U = (1 << n) - 1;
LOG2[0] = -1;
for(int i = 1; i <= U; i++) LOG2[i] = LOG2[i >> 1] + 1;
for(int i = 1; i <= n; i++){
for(int j = 0; j <= U; j++) f[i][j] = INF;
for(int j = 1; j <= n; j++) mp[i][j] = INF;
}
for(int i = 0; i <= U; i++)
for(int j = 0; j <= U; j++) g[i][j] = INF;
}
signed main(){
n = read(), m = read();
init();
for(int i = 1; i <= m; i++){
int u = read(), v = read(), w = read();
if(mp[u][v] <= w) continue;
mp[u][v] = w;
mp[v][u] = w;
}
for(int i = 1; i <= U; i++){
g[i][0] = 0;
int j = U & (~i);
int p = 0;
for(int s = j; s; s = (s - 1) & j){
pre[s] = p;
p = s;
}
for(; p; p = pre[p]){//要从小到大递推
int u = LOG2[lowbit(p)] + 1;
int c = INF;
for(int v = 1; v <= n; v++) if(i & (1 << (v - 1)))
c = min(c, mp[v][u]);
g[i][p] = min(g[i][p], g[i][p - lowbit(p)] + c);//lowbit妙用
}
}
for(int i = 1; i <= n; i++) f[1][1 << (i - 1)] = 0;
ans = min(ans, f[1][U]);
for(int i = 2; i <= n; i++){
for(int j = 0; j <= U; j++){ //已经加入树的集合
int m = U & (~j);
for(int s = m; s; s = (s - 1) & m){ //新加入的点
f[i][j | s] = min(f[i][j | s], f[i - 1][j] + (i - 1) * g[j][s]);
}
ans = min(ans, f[i][U]);
}
write(ans);
return 0;
}