Luogu3959 NOIP2017 宝藏 状压DP

题目传送门:https://www.luogu.org/problemnew/show/P3959

题意:给出一个有$N$个点的图,求其中的一个生成树(指定一个点为根),使得$\sum\limits_{i=1}^{N-1} v_i \times dep_i$最小,其中$v_i$为生成树上某条边的边权,$dep_i$为这条边连接的两个点中深度较浅的点的深度。$N \leq 12 , v \leq 5 \times 10^5$


 

$N \leq 12$给我们一个很强烈的信息:状态压缩

(所以这题还可以用暴力枚举+可持久化并查集拿90pts,用%你退火和诡异贪心拿到满分)

我们不妨考虑一层一层加入点

设$g_{i,j}$表示点集$j$向点集$i$每个点连一条边的最小权值和,可以在$O(3^NN^2)$的复杂度内算出(似乎可以优化成$O(3^NN)$)

然后设$f_{i,j}$表示现在的生成树中有$i$层节点,其中深度最深的节点集合为$j$时的最小权值,考虑转移为$f_{i+1,k}=f_{i,j}+g_{j,k}$

最后答案为$min\{f_{k,(1<<N)-1}\}$

复杂度为$O(3^NN^2)$,实际可优化成$O(3^NN)$

 1 // luogu-judger-enable-o2
 2 #include<bits/stdc++.h>
 3 #define MAXN (1 << 12) + 1
 4 using namespace std;
 5 
 6 inline int read(){
 7     int a = 0;
 8     char c = getchar();
 9     while(!isdigit(c))
10         c = getchar();
11     while(isdigit(c)){
12         a = (a << 3) + (a << 1) + (c ^ '0');
13         c = getchar();
14     }
15     return a;
16 }
17 
18 inline int min(int a , int b){
19     return a < b ? a : b;
20 }
21 
22 int f[MAXN][MAXN] , g[13][MAXN] , route[13][13];
23 
24 int main(){
25     memset(route , 0x3f , sizeof(route));
26     memset(g , 0x3f , sizeof(g));
27     int N = read() , M = read();
28     if(N == 1){
29         cout << 0;
30         return 0;
31     }
32     for(int i = 0 ; i < N ; i++)
33         g[0][1 << i] = 0;
34     while(M--){
35         int a = read() , b = read() , c = read();
36         route[a][b] = route[b][a] = min(route[a][b] , c);
37     }
38     for(int i = 1 ; i < 1 << N ; i++){
39         int s = ((1 << N) - 1) ^ i;
40         for(int j = s ; j ; j = j - 1 & s){
41             int p = j;
42             for(int k = p & -p ; p ; k = p & -p){
43                 int minN = 0x3f3f3f3f;
44                 p -= k;
45                 int t = log2(k) + 1 , q = i;
46                 for(int m = q & -q ; q ; m = q & -q){
47                     q -= m;
48                     minN = min(minN , route[t][(int)log2(m) + 1]);
49                 }
50                 if(minN == 0x3f3f3f3f){
51                     f[i][j] = 0x3f3f3f3f;
52                     break;
53                 }
54                 f[i][j] += minN;
55             }
56         }
57     }
58     for(int i = 1 ; i < N ; i++)
59         for(int j = 1 ; j < 1 << N ; j++){
60             if(j == (j & -j))
61                 continue;
62             for(int k = j - 1 & j ; k ; k = k - 1 & j)
63                 if(f[j ^ k][k] != 0x3f3f3f3f && g[i - 1][j ^ k] != 0x3f3f3f3f)
64                     g[i][j] = min(g[i][j] , g[i - 1][j ^ k] + f[j ^ k][k] * i);
65         }
66     int all = 0x7fffffff;
67     for(int i = 1 ; i < N ; i++)
68         all = min(all , g[i][(1 << N) - 1]);
69     cout << all;
70     return 0;
71 }

 

转载于:https://www.cnblogs.com/Itst/p/9763016.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值