HDU 4085 斯坦纳树

题目大意:

给定无向图,让前k个点都能到达后k个点(保护地)中的一个,而且前k个点每个需要占据后k个中的一个,相互不冲突

找到实现这个条件达到的选择边的最小总权值

这里很容易看出,最后选到的边不保证整个图是联通的

我们只要计算出每一个连通的最小情况,最后跑一遍dfs就能计算出答案了

那么用dp[i][j]表示 i 点为根得到联通状态为 j 的情况需要选到的边的最小总权值

这个用斯坦纳树的思想就可以做到的

对于每一个状态,都用spfa跑一遍得到最优解

dp[i][j] = min(dp[i][j] , dp[k][j]+w[i][k])

而对于每一个点来说就有 dp[i][j] = min(dp[i][j] , dp[i][k]+dp[i][j-k])  (k&j = k)

最后选出所有符合的状态的联通块,dfs找到最优解

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <vector>
  4 #include <queue>
  5 #include <iostream>
  6 using namespace std;
  7 #define pii pair<int,int>
  8 const int MAX = 1<<11;
  9 const int INF = 0x3f3f3f3f;
 10 int T , n , m , k;
 11 vector<pii> vec[55];
 12 int dp[55][MAX] , id[55];
 13 bool inq[55];
 14 queue<int> que;
 15
 16 void init()
 17 {
 18     for(int i=1 ; i<=n ; i++) vec[i].clear();
 19     memset(dp , 0x3f , sizeof(dp));
 20 }
 21
 22 void add_edge(int u , int v , int w)
 23 {
 24     vec[u].push_back(make_pair(v , w));
 25     vec[v].push_back(make_pair(u , w));
 26 }
 27
 28 void spfa(int cur)
 29 {
 30     while(!que.empty()){
 31         int u = que.front();
 32       // cout<<"inq: "<<u<<endl;
 33         que.pop(); inq[u]=false;
 34         int l = vec[u].size();
 35         for(int i=0 ; i<l ; i++){
 36             int to = vec[u][i].first;
 37             if(dp[to][cur] > dp[u][cur]+vec[u][i].second){
 38                 dp[to][cur] = dp[u][cur]+vec[u][i].second;
 39                 if(!inq[to]){
 40                     inq[to] = true;
 41                     que.push(to);
 42                 }
 43             }
 44         }
 45     }
 46 }
 47
 48 void get_id()
 49 {
 50     memset(id , 0 , sizeof(id));
 51     for(int i=1 ; i<=k ; i++){
 52         id[i] = i , dp[i][1<<(id[i]-1)] = 0;
 53         que.push(i);
 54         spfa(1<<(id[i]-1));
 55     }
 56     for(int i=k+1 , j=n; i<=2*k ; i++ , j--){
 57         id[j] = i , dp[j][1<<(id[j]-1)] = 0;
 58         que.push(j);
 59         spfa(1<<(id[j]-1));
 60     }
 61     for(int i=k+1 ; i<=n-k ; i++) dp[i][0] = 0;
 62 }
 63
 64 void read_edge()
 65 {
 66     for(int i=0 ; i<m ; i++) {
 67         int u , v , w;
 68         scanf("%d%d%d" , &u , &v , &w);
 69         add_edge(u , v , w);
 70     }
 71 }
 72
 73 void solve()
 74 {
 75     int all = 1<<(2*k);
 76     for(int cur=0 ; cur<all ; cur++){
 77         for(int i=1 ; i<=n ; i++){
 78             for(int p=cur&(cur-1) ; p ; p=(p-1)&cur){
 79                 if(dp[i][cur] > dp[i][p]+dp[i][cur-p]){
 80                     dp[i][cur] = dp[i][p]+dp[i][cur-p];
 81                     if(!inq[i]){
 82                         que.push(i);
 83                         inq[i]=true;
 84                     }
 85                 }
 86             }
 87         }
 88         spfa(cur);
 89     }
 90 }
 91
 92 //求出得到的子树的最小代价
 93 int tree[MAX] , ok[MAX] , tot , ret; //ok[]记录那些合法的状态,也就是左半部分的1等于右半部分的1
 94
 95 bool is_ok(int x)
 96 {
 97     int cnt = 0;
 98     for(int i=0 ; i<k ; i++) if(x&(1<<i)) cnt++;
 99     for(int i=k ; i<2*k ; i++) if(x&(1<<i)) cnt--;
100     return cnt == 0;
101 }
102
103 void get_tree()
104 {
105     int all = 1<<(2*k);
106     tot = 0;
107     for(int cur=0 ; cur<all ; cur++){
108         tree[cur] = INF;
109         for(int i=1 ; i<=n ; i++) tree[cur] = min(tree[cur] , dp[i][cur]);
110         if(cur>0 && is_ok(cur)){
111             ok[++tot] = cur;
112            // cout<<tot<<" "<<cur<<" "<<tree[cur]<<endl;
113         }
114     }
115 }
116
117 void dfs(int p , int cur , int cost , int all)
118 {
119     if(cost>ret) return;
120     if(cur == all){
121         ret = min(ret , cost);
122         return;
123     }
124     if(p>tot) return;
125     if(!(cur&ok[p])) dfs(p+1 , cur|ok[p] , cost+tree[ok[p]] , all);
126     dfs(p+1 , cur , cost , all);
127 }
128
129 int main()
130 {
131    // freopen("a.in" , "r" , stdin);
132     scanf("%d" , &T);
133     while(T--)
134     {
135         scanf("%d%d%d" , &n , &m , &k);
136         init();
137         read_edge();
138         get_id();
139         solve();
140         get_tree();
141         ret = INF;
142         dfs(1 , 0 , 0 , (1<<(2*k))-1);
143         if(ret<INF) printf("%d\n" , ret);
144         else puts("No solution");
145     }
146     return 0;
147 }
时间: 2024-10-10 17:34:32

HDU 4085 斯坦纳树的相关文章

HDU 4085 斯坦纳树模板题

Dig The Wells Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 971    Accepted Submission(s): 416 Problem Description You may all know the famous story "Three monks". Recently they find som

HDU 4085 斯坦纳树+DP

https://cn.vjudge.net/problem/HDU-4085 给你n,m,k ,分别表示有n个点,m条边,每条边有一个权值,表示修复这条边需要的代价 从前k个点中任取一个使其和后k个点中的某一个点,通过边连接,并且必须是一一对应,问最小的代价是多少. 原文地址:https://www.cnblogs.com/Aragaki/p/10989578.html

HDU 4085 Peach Blossom Spring 记忆化搜索枚举子集 斯坦纳树

题目链接:点击打开链接 题意: 第一行输入n个点 m条可修建的无向边 k个人 下面给出修建的边和修建该边的花费. 开始时k个人在1-k的每个点上(一个点各一人) 目标:从m条给定边中修建部分边使得花费和最小 让k个人移动到 [n-k+1, n] 后面的k个点上(每个点放一个人). 思路: 首先就是一道斯坦纳树,还是先求一个dp数组(求解方法:点击打开链接) dp[i][j] 表示以i为根 ,j为8个点中是否在 i 的子树里 时的最小花费. 现在的问题就是如何求答案. 因为一个人到他的目标点这条路

HDU 4085 Peach Blossom Spring 斯坦纳树 状态压缩DP+SPFA

状态压缩dp+spfa解斯坦纳树 枚举子树的形态 dp[i][j] = min(dp[i][j], dp[i][k]+dp[i][l]) 其中k和l是对j的一个划分 按照边进行松弛 dp[i][j] = min(dp[i][j], dp[i'][j]+w[i][j])其中i和i'之间有边相连 #include <cstdio> #include <cstring> #include <queue> using namespace std; const int maxn

FJoi2017 1月20日模拟赛 直线斯坦纳树(暴力+最小生成树+骗分+人工构造+随机乱搞)

[题目描述] 给定二维平面上n个整点,求该图的一个直线斯坦纳树,使得树的边长度总和尽量小. 直线斯坦纳树:使所有给定的点连通的树,所有边必须平行于坐标轴,允许在给定点外增加额外的中间节点. 如下图所示为两种直线斯坦纳树的生成方案,蓝色点为给定的点,红色点为中间节点. [输入格式] 第一行一个整数n,表示点数. 以下n行,每行两个整数表示点的x,y坐标. [输出格式] 第一行一个整数m,表示中间节点的个数. 必须满足m <= 10 * n 以下m行,每行2个整数表示中间节点的坐标. 以下n+m-1

BZOJ 3205 [Apio2013]机器人 ——斯坦纳树

腊鸡题目,实在卡不过去. (改了一下午) 就是裸的斯坦纳树的题目,一方面合并子集,另一方面SPFA迭代求解. 优化了许多地方,甚至基数排序都写了. 还是T到死,不打算改了,就这样吧 #include <map> #include <cmath> #include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm

【Foreign】修路 [斯坦纳树]

修路 Time Limit: 20 Sec  Memory Limit: 256 MB Description Input Output 仅一行一个整数表示答案. Sample Input 5 5 2 1 3 4 3 5 2 2 3 1 3 4 4 2 4 3 Sample Output 9 HINT Main idea 给定若干对点,选择若干边,询问满足每对点都连通的最小代价. Source 发现 d 非常小,所以我们显然可以使用斯坦纳树来求解. 斯坦纳树是用来解决这种问题的:给定若干关键点,

BZOJ 2595 [Wc2008]游览计划 ——斯坦纳树

[题目分析] 斯坦纳树=子集DP+SPFA? 用来学习斯坦纳树的模板. 大概就是用二进制来表示树包含的点,然后用跟几点表示树的形态. 更新分为两种,一种是合并两个子集,一种是换根,换根用SPFA迭代即可. [代码] #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <set> #include <map> #include

【BZOJ2595】【Wc2008】游览计划、斯坦纳树

题解:斯坦纳树,实现神马的在代码里面有还看得过去的注释. 代码: #include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define N 15 #define inf 0x3f3f3f3f using namespace std; const int dx[]={0,0,1,-1}; const int dy[