题目描述

村子间的小路年久失修,为了保障村子之间的往来,AAA君决定带领大家修路。

村子可以看做是一个边带权的无向图GGG, GGG 由 nnn 个点与 mmm 条边组成,图中的点从 1∼n1 \sim n1∼n 进行编号。现在请你选择图中的一些边,使得 ∀1≤i≤d\forall 1 \leq i \leq d∀1≤i≤d , iii 号点和 n−i+1n - i + 1n−i+1号点可以通过你选择出的那些边连通,并且你要最小化选出的所有边的权值和。请你告诉AAA君这个最小权值和。

输入格式

第一行三个整数 nnn, mmm , ddd 表示图中的点数、边数与限制条件。

接下来 mmm 行每行三个整数 uiu_iu​i​​, viv_iv​i​​ , wiw_iw​i​​ 表示一条连接 (ui,vi)(u_i, v_i)(u​i​​,v​i​​) 的权值为叫的无向边。

输出格式

仅一行一个整数表示答案。若无解输出 −1-1−1 .

样例

样例输入

5 5 2
1 3 4
3 5 2
2 3 1
3 4 4
2 4 3

样例输出

9
 #include <bits/stdc++.h>

 using namespace std;
const int maxn = 1e4+;
const int inf = <<;
int fa[maxn];
int n,m,d,ST;
int s[maxn];
bool inq[maxn][<<];
int f[maxn][<<];//f[i][j]根节点为i,连同状态为j的最小生成树
int g[<<];// g 连同状态为j的最小生成树
int ehead[maxn],ecnt;
queue<pair<int,int> >que;
inline void read(int&a){char c;while(!(((c=getchar())>='')&&(c<='')));a=c-'';while(((c=getchar())>='')&&(c<=''))(a*=)+=c-'';}
struct edge
{
int u,v,w,next;
}edg[maxn*];
bool upd(int &u,int v)
{
return u>v?(u=v,):;
}
void add (int u,int v,int w)
{
edg[++ecnt] = (edge){u,v,w,ehead[u]};
ehead[u]=ecnt;
edg[++ecnt] = (edge){v,u,w,ehead[v]};
ehead[v]=ecnt; }
int findd (int x)
{
if (fa[x]==x) return x;
else return fa[x]=findd(fa[x]);
}
void Union (int x,int y)
{
int fx = findd(x),fy = findd(y);
if (fx==fy) return ;
else {
fa[fx] = fy;
return ;
}
}
void spfa ()
{
while (!que.empty()){
pair<int,int> h = que.front();
que.pop();
int u = h.first,t = h.second;
inq[u][t] = false;
for (int j=ehead[u];j;j=edg[j].next){
int v = edg[j].v,k = s[v]|t;
if (upd(f[v][k],f[u][t]+edg[j].w)&&k==t){
if (!inq[v][k]){
que.push(make_pair(v,k));
inq[v][k] = true;
}
}
}
}
}
bool check (int j)
{
for (int i=;i<d;++i){
int a = (j>>i)&;
int b = (j>>(i+d))&;
if (a!=b) return false;
}
return true;
}
void init ()
{
ecnt = ;
for (int i=;i<maxn;++i)
fa[i] = i;
memset(inq,false,sizeof inq);
}
int main()
{
//freopen("road10.in","r",stdin);
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
read(n);read(m);read(d);
//scanf("%d%d%d",&n,&m,&d);
init();
ST=(<<(*d))-;
for (int i=;i<=n;++i)
for (int j=;j<=ST;++j)
f[i][j] = inf;
for (int i=;i<m;++i){
int u,v,w;
read(u);read(v);read(w);
add(u,v,w);
Union(u,v);
}
for (int i=;i<=d;++i){
s[i] = <<(i-);
f[i][s[i]] = ;
s[n-i+] = <<(i-+d);
f[n-i+][s[n-i+]] = ;
if (findd(i)!=findd(n-i+)){
printf("-1\n");
return ;
}
}
for (int j=;j<=ST;++j){
for (int i=;i<=n;++i){
for (int k=j;k>;k=((k-)&j)){//枚举j的子集k
upd(f[i][j],f[i][k|s[i]]+f[i][(j^k)|s[i]]);//当前根节点为i 更新
}
}
for (int i=;i<=n;++i){
if (f[i][j]<inf){
inq[i][j] = true;
que.push(make_pair(i,j));
}
}
spfa();
}
for (int j=;j<=ST;++j){
g[j] = inf;
if (!check(j)) continue ;
for (int i=;i<=n;++i)
upd(g[j],f[i][j]);
}
for (int j=;j<=ST;++j){
for (int k=j;k>;k=(k-)&j){
upd(g[j],g[k]+g[k^j]);
}
}
printf("%d\n",g[ST]);
return ;
}

转载:http://blog.csdn.net/gzh1992n/article/details/9119543

1. 什么是斯坦纳树?

斯坦纳树问题是组合优化学科中的一个问题。将指定点集合中的所有点连通,且边权总和最小的生成树称为最小斯坦纳树(Minimal Steiner Tree),其实最小生成树是最小斯坦纳树的一种特殊情况。而斯坦纳树可以理解为使得指定集合中的点连通的树,但不一定最小。

2. 如何求解最小斯坦纳树?

可以用DP求解,dp[i][state]表示以i为根,指定集合中的点的连通状态为state的生成树的最小总权值。

转移方程有两重:

第一重,先通过连通状态的子集进行转移。

dp[i][state]=min{ dp[i][subset1]+dp[i][subset2] }

枚举子集的技巧可以用 for(sub=(state-1)&state;sub;sub=(sub-1)&state)。

第二重,在当前枚举的连通状态下,对该连通状态进行松弛操作。

dp[i][state]=min{ dp[i][state], dp[j][state]+e[i][j] }

为什么只需对该连通状态进行松弛?因为更后面的连通状态会由先前的连通状态通过第一重转移得到,所以无需对别的连通状态松弛。松弛操作用SPFA即可。

复杂度 O(n*3^k+cE*2^k)

c为SPFA复杂度中的常数,E为边的数量,但几乎达不到全部边的数量,甚至非常小。3^k来自于子集的转移sum{C(i,n)*2^i} (1<=i<=n),用二项式展开求一下和。

 /*
* Steiner Tree:求,使得指定K个点连通的生成树的最小总权值
* st[i] 表示顶点i的标记值,如果i是指定集合内第m(0<=m<K)个点,则st[i]=1<<m
* endSt=1<<K
* dptree[i][state] 表示以i为根,连通状态为state的生成树值
*/
#define CLR(x,a) memset(x,a,sizeof(x)) int dptree[N][<<K],st[N],endSt;
bool vis[N][<<K];
queue<int> que; int input()
{
/*
* 输入,并且返回指定集合元素个数K
* 因为有时候元素个数需要通过输入数据处理出来,所以单独开个输入函数。
*/
} void initSteinerTree()
{
CLR(dptree,-);
CLR(st,);
for(int i=;i<=n;i++) CLR(vis[i],);
endSt=<<input();
for(int i=;i<=n;i++)
dptree[i][st[i]]=;
} void update(int &a,int x)
{
a=(a>x || a==-)? x : a;
} void SPFA(int state)
{
while(!que.empty()){
int u=que.front();
que.pop();
vis[u][state]=false;
for(int i=p[u];i!=-;i=e[i].next){
int v=e[i].vid;
if(dptree[v][st[v]|state]==- ||
dptree[v][st[v]|state]>dptree[u][state]+e[i].w){ dptree[v][st[v]|state]=dptree[u][state]+e[i].w;
if(st[v]|state!=state || vis[v][state])
continue; //只更新当前连通状态
vis[v][state]=true;
que.push(v);
}
}
}
} void steinerTree()
{
for(int j=;j<endSt;j++){
for(int i=;i<=n;i++){
if(st[i] && (st[i]&j)==) continue;
for(int sub=(j-)&j;sub;sub=(sub-)&j){
int x=st[i]|sub,y=st[i]|(j-sub);
if(dptree[i][x]!=- && dptree[i][y]!=-)
update(dptree[i][j],dptree[i][x]+dptree[i][y]);
}
if(dptree[i][j]!=-)
que.push(i),vis[i][j]=true;
}
SPFA(j);
}
}
 
 

「长乐集训 2017 Day8」修路 (斯坦纳树)的更多相关文章

  1. 「长乐集训 2017 Day10」划分序列 (二分 dp)

    「长乐集训 2017 Day10」划分序列 题目描述 给定一个长度为 n nn 的序列 Ai A_iA​i​​,现在要求把这个序列分成恰好 K KK 段,(每一段是一个连续子序列,且每个元素恰好属于一 ...

  2. loj6271 「长乐集训 2017 Day10」生成树求和 加强版(矩阵树定理,循环卷积)

    loj6271 「长乐集训 2017 Day10」生成树求和 加强版(矩阵树定理,循环卷积) loj 题解时间 首先想到先分开三进制下每一位,然后每一位分别求结果为0,1,2的树的个数. 然后考虑矩阵 ...

  3. loj6271「长乐集训 2017 Day10」生成树求和 加强版

    又是一个矩阵树套多项式的好题. 这里我们可以对每一位单独做矩阵树,但是矩阵树求的是边权积的和,而这里我们是要求加法,于是我们i将加法转化为多项式的乘法,其实这里相当于一个生成函数?之后如果我们暴力做的 ...

  4. LOJ#6271. 「长乐集训 2017 Day10」生成树求和 加强版

    传送门 由于是边权三进制不进位的相加,那么可以考虑每一位的贡献 对于每一位,生成树的边权相当于是做模 \(3\) 意义下的加法 考虑最后每一种边权的生成树个数,这个可以直接用生成函数,在矩阵树求解的时 ...

  5. 「长乐集训 2017 Day1」区间 线段树

    题目 对于两个区间\((a,b),(c,d)\),若\(c < a < d\)或\(c < b < d\)则可以从\((a,b)\)走到\((c,d)\)去,现在有以下两种操作 ...

  6. LOJ #6044 -「雅礼集训 2017 Day8」共(矩阵树定理+手推行列式)

    题面传送门 一道代码让你觉得它是道给初学者做的题,然鹅我竟没想到? 首先考虑做一步转化,我们考虑将整棵树按深度奇偶性转化为一张二分图,即将深度为奇数的点视作二分图的左部,深度为偶数的点视作二分图的右部 ...

  7. LOJ_6045_「雅礼集训 2017 Day8」价 _最小割

    LOJ_6045_「雅礼集训 2017 Day8」价 _最小割 描述: 有$n$种减肥药,$n$种药材,每种减肥药有一些对应的药材和一个收益. 假设选择吃下$K$种减肥药,那么需要这$K$种减肥药包含 ...

  8. 【LYOI 212】「雅礼集训 2017 Day8」价(二分匹配+最大权闭合子图)

    「雅礼集训 2017 Day8」价 内存限制: 512 MiB时间限制: 1000 ms 输入文件: z.in输出文件: z.out   [分析] 蛤?一开始看错题了,但是也没有改,因为不会做. 一开 ...

  9. loj #6046. 「雅礼集训 2017 Day8」爷

    #6046. 「雅礼集训 2017 Day8」爷 题目描述 如果你对山口丁和 G&P 没有兴趣,可以无视题目背景,因为你估计看不懂 …… 在第 63 回战车道全国高中生大赛中,军神西住美穗带领 ...

随机推荐

  1. p5471 [NOI2019]弹跳

    分析 代码 #include<bits/stdc++.h> using namespace std; #define fi first #define se second #define ...

  2. pve三种操作方式

    pve三种操作方式 ==========================================================api方式 https://192.168.1.4:8006/p ...

  3. html基础与表格的理解·

    1.静态网页与动态网页的区别:是否访问数据库 2.超文本:超文本是指超出文本的范围,可以插入声音视频,表格图片等 3.标记语言与网页结构:标记语言就是标签,网页结构包含<html>< ...

  4. C#通过UserAgent判断智能设备(Android,IOS,PC,Mac)

    尝试通过 Agent 来获取相应的智能手机设备标识,根据标识的不同来输出对应设备所需的显示样式及其他.经过努力,终于搜集了比较全的 智能设备 的 Agent,相应的判断过程及代码如下,不明白的留言. ...

  5. 【EWM系列】SAP EWM模块-部分流程图

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[EWM系列]SAP EWM模块-部分流程图   ...

  6. LeetCode 230. Kth Smallest Element in a BST 动态演示

    返回排序二叉树第K小的数 还是用先序遍历,记录index和K进行比较 class Solution { public: void helper(TreeNode* node, int& idx ...

  7. SQL常用语句之数据库的创建、删除以及属性的修改-篇幅1

    本篇文章主要总结了SQL Server 语句的使用和一些基础知识,因为目前我也正在学习,所以总结一下. 要使用数据库语句,首先就要知道数据库对象的结构: 通常情况下,如果不会引起混淆,可以直接使用对象 ...

  8. HDFS-Suffle

    一.Shuffle机制 1.官网图 2.MR确保每个Reducer的输入都是按照key排序的.系统执行排序的过程(即将Mapper输出作为输入传给Reducer)成为Shuffle 二.Partiti ...

  9. AC自动机题单

    AC自动机题目 真的超级感谢xzy 真的帮到我很多 题单 [X] [luogu3808][模板]AC自动机(简单版) https://www.luogu.org/problemnew/show/P38 ...

  10. 专题:性能调优之工具---perf

    1. Linux Perf简介 1.1 Perf是什么 Perf 是内置于Linux 内核源码树中的性能剖析(profiling)工具.它基于事件采样原理,以性能事件为基础,支持针对处理器相关性能指标 ...