洛谷

洛谷题单指南-状态压缩动态规划-P3959 [NOIP 2017 提高组] 宝藏

原题链接:https://www.luogu.com.cn/problem/P3959

题意解读:n个节点m条边的带权无向图,从任意一个点start开始遍历,每次已访问节点集合中选取一个点u,扩展一个未访问过的点v,扩展点v的代价是从start到u的深度 * u到v的权值,直到访问完所有节点,求所有方案中代价的最小值。

解题思路:

本题的搜索解法可以参考https://www.cnblogs.com/hackerchef/p/18733710

这里给出状压DP的解决方案。

从题意分析可知,最终扩展出的是一棵树,因为如果不是树,必然形成环,可以去掉环中的一条边,总代价变小。

由于树是按层来扩展的,从第i层扩展到第i+1层的最低代价可以计算出来,如此我们可以设计状态。

设f[i][j]表示当前扩展到第i层,前i层点的状态是j的最小代价。状态用二进制位是来表示包含哪些点,如1011表示包含点0,1,3。

设前i-1层的状态是k,设从状态a扩展到状态b的最小代价是cost[a][b],那么有:

对于所有的状态k

f[i][j] = min(f[i][j], f[i-1][k] + cost[k][j])

前i-1层的状态必然是前i层状态的子集,可以通过枚举二进制的子集来优化时间:for(int k = j; k; k = (k - 1) & j)

另外,可以提前预处理出cost[a][b]。

初始化:对于起点x,f[0][1 << x] = 0,其余为INF。

结果:取f[][(1 << n) - 1]的最小值。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 12, INF = 0x3f3f3f3f;
int g[N][N];
int f[N][1 << N], cost[1 << N][1 << N];
int n, m, ans = INF;

void init()
{
    for(int i = 1; i < 1 << n; i++) //枚举所有状态
    {
        for(int j = i; j; j = (j - 1) & i) //枚举所有i的子集
        {
            int k = i ^ j; //k表示从j到i最新扩展出的一层状态
            //计算所有从i到k中所有点的最小代价,累加到cost[j][i]
            for(int s = 0; s < n; s++)
            {
                if(k >> s & 1)
                {
                    int tmp = INF;
                    for(int t = 0; t < n; t++)
                    {
                        if(j >> t & 1)
                        {
                            tmp = min(tmp, g[t][s]);
                        }
                    }
                    if(tmp == INF) 
                    {
                        cost[j][i] = INF; //状态转移不合法
                        break;
                    }
                    else cost[j][i] += tmp;
                }
            }
        }
    }
}

void dp(int x)
{
    memset(f, 0x3f, sizeof(f));
    f[0][1 << x] = 0;
    for(int i = 1; i < n; i++) //枚举所有深度
    {
        for(int j = 1; j < 1 << n; j++) //枚举所有状态
        {
            for(int k = j; k; k = (k - 1) & j) //枚举所有子集
            {   
                if(cost[k][j] != INF)
                    f[i][j] = min(f[i][j], f[i - 1][k] + i * cost[k][j]);
            }
            if(j == (1 << n) - 1) ans = min(ans, f[i][j]);
        }
    }
}

int main()
{
    cin >> n >> m;
    if(n == 1) //只有一个点的情况
    {
        cout << 0;
        return 0;
    }
    memset(g, 0x3f, sizeof(g));
    for(int i = 1; i <= m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        u--, v--; //编号从0开始
        g[u][v] = g[v][u] = min(g[u][v], w);
    }
    init();
    for(int i = 0; i < n; i++) dp(i);  
    cout << ans;
    return 0;
}