P3959 [NOIP2017 提高组] 宝藏

Problem

参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 \(n\) 个深埋在地下的宝藏屋, 也给出了这 \(n\) 个宝藏屋之间可供开发的 \(m\) 条道路和它们的长度。

小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。

小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。

在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。

新开发一条道路的代价是 \(\mathrm{L} \times \mathrm{K}\)。其中 \(L\) 代表这条道路的长度,\(K\) 代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。

请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。
\(1 \le n \le 12,0 \le m \le 10^3,v \le 5 \times 10^5\)

Solution

Thinking 1

发现题目就是要找一个生成树,使得代价最小。

Thinking 2

发现\(1 \le n \le 12\),想到倍增dp。

Thinking 3

发现如果我们定一个点为第一个开凿的点,即树的根。发现开辟道路的代价和开辟的时间没有关系。

Thinking 4

为了有序化,我们设\(dp[i][j]\)为当前树深度为\(i\),状态为\(j\)的最小代价。
可以想到\(dp[i][j] = \min\{dp[i - 1][k] + cost\}\)。考虑\(k\)的限制:

  1. \(k \subseteq j\)
  2. \(k\)必须通过每个点最多拓展一层到\(j\)
    我们预处理出\(g_i\),表示状态为\(i\)扩展\(1\)层最多能到的点的状态。那么第二个可以表示为\(j \subseteq g_k\)
    cost就是从\(k\)变成\(j\)的边权和*i。
# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N = 12;
const int inf = 0x3f3f3f3f;
int n,m;
int w[N][N];
int g[(1 << N)];
int dp[N][(1 << N)]; // dp[dep][state]
signed main(void)
{
    scanf("%lld%lld",&n,&m);
    memset(w,0x3f,sizeof(w));
    for(int i = 1; i <= m; i++)
    {
        int u,v,W;
        scanf("%lld%lld%lld",&u,&v,&W); --u,--v;
        w[u][v] = w[v][u] = min(w[u][v],W);
    }
    for(int i = 1; i < (1 << n); i++)
    {
        for(int j = 0; j < n; j++)
        {
            if(i >> j & 1) // j \in i
            {
                for(int k = 0; k < n; k++) 
                {
                    if(w[j][k] != inf)
                    g[i] = g[i] | (1 << k);
                }
            }
        }
    }
    memset(dp,0x3f,sizeof(dp));
    for(int i = 0; i < n; i++) dp[0][1 << i] = 0;
    
    for(int i = 1; i < (1 << n); i++)
    {
        for(int j = i - 1; j; j = (j - 1) & i)
        {
            int State = i xor j;
            if(g[j] & i != i) continue; // i \subset g[j]
            int total = 0;
            for(int k = 0; k < n; k++)
            {
                if(State >> k & 1)
                {
                    int minl = inf;
                    for(int l = 0; l < n; l++)
                    {
                        if(j >> l & 1)
                        {
                            minl = min(minl,w[l][k]);
                        }
                    }
                    total += minl;
                }
            }
            for(int k = 1; k < n; k++) 
            {dp[k][i] = min(dp[k][i],dp[k - 1][j] + total * k);}
        }
    }
    int ans = inf;
    for(int i = 0; i < n; i++) ans = min(ans,dp[i][(1 << n) - 1]);
    printf("%lld\n",ans);
    return 0;
}
posted @ 2021-07-04 10:37  luyiming123  阅读(66)  评论(0编辑  收藏  举报