LOJ 2318 「NOIP2017」宝藏

题面

题目传送门

解法

为什么我的状压dp那么丑啊……

  • 发现 n ≤ 12 n≤12 n12,所以不妨考虑状压dp
  • f [ d ] [ S ] [ r t ] f[d][S][rt] f[d][S][rt]表示当前深度为 d d d,在根为 r t rt rt的子树中有 S S S集合内的点的最小代价
  • 考虑先枚举 r t rt rt的一个儿子是什么,假设为 x x x,然后枚举集合 S ′ ⊂ S S'\subset S SS作为 x x x的子树部分,其它剩下的部分继续作为除 x x x以外的子树部分
  • 可以发现这个dp很好转移: f [ d ] [ S ] [ r t ] = m i n ( d × v [ r t ] [ x ] + f [ d + 1 ] [ S ′ ] [ x ] + f [ d ] [ S − S ′ ] [ r t ] ) f[d][S][rt]=min(d×v[rt][x]+f[d+1][S'][x]+f[d][S-S'][rt]) f[d][S][rt]=min(d×v[rt][x]+f[d+1][S][x]+f[d][SS][rt])
  • 时间复杂度: O ( 3 n n 3 ) O(3^nn^3) O(3nn3)
  • 为什么是这个复杂度呢?因为枚举子集的整体复杂度为 O ( 3 n ) O(3^n) O(3n),这个可以使用二项式定理来证明。关于 n n n的状态为 O ( n 2 ) O(n^2) O(n2),转移的复杂度为 O ( n ) O(n) O(n),所以总复杂度为 O ( 3 n n 3 ) O(3^nn^3) O(3nn3)

代码

#include 
#define inf 1ll << 40
#define ll long long
#define N 13
using namespace std;
ll lg[1 << N], a[N][N], f[N][1 << N][N];
int lowbit(int x) {return x & -x;}
ll dp(int d, int S, int rt) {
    if (!S) return 0;
    if (f[d][S][rt]) return f[d][S][rt];
    ll ret = inf;
    for (int tx = S, ty = lowbit(S); tx; tx -= ty, ty = lowbit(tx)) {
        int x = lg[ty], y = S ^ ty; ll tmp = a[rt][x] * d;
        for (int s = y; s; s = (s - 1) & y)
            ret = min(ret, tmp + dp(d + 1, s, x) + dp(d, y ^ s, rt));
        ret = min(ret, tmp + dp(d, y, rt));
    }
    return f[d][S][rt] = ret;
}
int main() {
    ios::sync_with_stdio(false);
    int n, m; cin >> n >> m;
    for (int i = 2; i < (1 << n); i++) lg[i] = lg[i >> 1] + 1;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            a[i][j] = inf;
    for (int i = 1; i <= m; i++) {
        int x, y; ll v; cin >> x >> y >> v;
        x--, y--; ll tx = min(a[x][y], v);
        a[x][y] = a[y][x] = tx;
    }
    ll ans = inf, mask = (1 << n) - 1;
    for (int i = 0; i < n; i++)
        ans = min(ans, dp(1, mask ^ (1 << i), i));
    cout << ans << "\n";
    return 0;
}

你可能感兴趣的:(【OJ】LOJ,【算法】dp,【算法】状压dp)