最小斯坦纳树算法介绍

最小斯坦纳树

  • 介绍
  • 解法
  • 例题

介绍

  • 现在有一个图,将它们作为全集 G = ( V , E ) G=(V,E) G=(V,E),我现在有一个这些点的子集 S S S S S S大概有十几个点,现在想从 G G G中选出一个子图 G ′ = ( V ′ , E ′ ) G'=(V',E') G=(V,E),使得 S ⊆ V ′ S\subseteq V' SV,且 G ′ G' G构成一个连通图的同时所有边的权值和最小

解法

  • 首先显然结果是一棵树,因为如果有环的话就可以断开环使得答案变得更小
  • 这种问题通常有一种思路,设 d p [ i ] [ S ] dp[i][S] dp[i][S]表示以 i i i为根节点,节点集合为 S S S的答案, T T T表示 S S S的子集,那么应该有 d p [ i ] [ S ] = m i n ( d p [ i ] [ S ] , d p [ i ] [ T ] + d p [ i ] [ S − T ] ) 1 ◯ dp[i][S]=min(dp[i][S],dp[i][T]+dp[i][S-T])\textcircled1 dp[i][S]=min(dp[i][S],dp[i][T]+dp[i][ST])1
  • 如果 i i i的度数为 1 1 1,那么有 d p [ i ] [ S ] = m i n ( d p [ i ] [ S ] , d p [ i ] [ S ] + w i , j ) 2 ◯ dp[i][S]=min(dp[i][S],dp[i][S]+w_{i,j})\textcircled2 dp[i][S]=min(dp[i][S],dp[i][S]+wi,j)2
  • 以上,递推扩展 S S S到最大,即为答案
  • 1 ◯ \textcircled{1} 1可以枚举子集得到, 2 ◯ \textcircled2 2是最短路的基本式,对于每个状态跑 d i j k s t r a dijkstra dijkstra即可
  • 简单说一下枚举子集,就是把状态对应的二进制数减去1和自己做与运算,这样一直进行下去即可,数学表达式如下 s = ( s − 1 ) & s s=(s-1)\And s s=(s1)&s
  • S S S的节点个数为 k k k,总的节点数为 n n n,枚举子集转移的时间复杂度为 O ( n × 3 k ) O(n\times 3^{k}) O(n×3k) d i j k s t r a dijkstra dijkstra转移的时间复杂度为 O ( n l o g n × 2 k ) O(nlogn\times2^{k}) O(nlogn×2k),总的时间复杂度是 O ( n × 3 k + n l o g n × 2 k ) O(n\times3^k+nlogn\times2^k) O(n×3k+nlogn×2k)

例题

https://www.luogu.com.cn/problem/P6192

  • 模板题,按照上面所述,转化为代码如下
#include <bits/stdc++.h>using namespace std;typedef long long ll;const int INF = 0x3f3f3f3f;int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int n, m, k;cin >> n >> m >> k;vector<vector<pair<int, int> > > g(n + 1);for(int i=0;i<m;i++) {int u, v, w;cin >> u >> v >> w;g[u].emplace_back(v, w);g[v].emplace_back(u, w);}int st = (1 << k);vector<vector<int> > dp(n + 1, vector<int>(st, INF));vector<int> key(k);priority_queue<pair<int, int>, vector<pair<int, int> >, greater< > > q;for(int i=0;i<k;i++) {cin >> key[i];dp[key[i]][1 << i] = 0;}function<void(int)> dijkstra = [&](int s) {vector<bool> vis(n + 1);while(!q.empty()) {auto u = q.top();q.pop();if(vis[u.second]) {continue;}vis[u.second] = true;for(auto v : g[u.second]) {if(dp[v.first][s] > dp[u.second][s] + v.second) {dp[v.first][s] = dp[u.second][s] + v.second;q.emplace(dp[v.first][s], v.first);}}}};for(int s=1;s<st;s++) {for(int i=1;i<=n;i++) {for(int sub=s;sub;sub=(sub-1)&s) {dp[i][s] = min(dp[i][s], dp[i][sub] + dp[i][s ^ sub]);}if(dp[i][s] != INF) {q.emplace(dp[i][s], i);}dijkstra(s);}}cout << dp[key[0]][st - 1];return 0;
}

https://www.luogu.com.cn/problem/P4294

  • 这题主要是要把题目抽象出来,题目是说要使用最少的志愿者所有景点串起来,且景点数量最多十个,恰好满足最小斯坦纳树的情景,也就是把景点看做上面描述的 S S S集合,要从 V V V中选出点集 V ′ V' V,使得 S ⊆ V ′ S\subseteq V' SV
  • 我们把所有的点编号,设 d p [ i ] [ s ] dp[i][s] dp[i][s]表示根节点为 i i i,所选景点集合为 S S S的所需志愿者的最少数量, a [ i ] a[i] a[i]表示第 i i i个景点所需的志愿者数量,按照最小斯坦纳树的想法,有下面的式子
    { d p [ i ] [ S ] = m i n ( d p [ i ] [ S ] , d p [ i ] [ S − T ] + d p [ i ] [ T ] − a [ i ] ) , d e g r e e [ i ] > 1 1 ◯ d p [ i ] [ S ] = m i n ( d p [ i ] [ S ] , d p [ j ] [ S ] + a [ i ] ) , d e g r e e [ i ] = 1 2 ◯ \left\{ \begin{aligned} dp[i][S]&=min(dp[i][S],dp[i][S-T]+dp[i][T]-a[i]),degree[i]>1\textcircled1\\ dp[i][S]&=min(dp[i][S],dp[j][S]+a[i]),degree[i]=1\textcircled2 \end{aligned} \right. {dp[i][S]dp[i][S]=min(dp[i][S],dp[i][ST]+dp[i][T]a[i]),degree[i]>11=min(dp[i][S],dp[j][S]+a[i]),degree[i]=12
  • 其中 T T T表示 S S S的子集,因为 1 ◯ \textcircled1 1式中,两个集合合并的时候根节点重复计算了两遍,所以需要减去一个 a [ i ] a[i] a[i]
  • 但是还有一个问题,怎么求方案?这里其实和普通动态规划的方案求法是类似的,都是每一次转移的时候记录一下上一步的操作,根据每一步操作的某些数据信息,最后算出结果之后逆序倒推,具体看代码
#include <bits/stdc++.h>using namespace std;typedef long long ll;const int INF = 0x3f3f3f3f;int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int n, m;cin >> n >> m;int tot = n * m;vector<int> a(tot);int s = 0;int rt = -1;for(int i=0;i<tot;i++) {cin >> a[i];if(a[i] == 0) {rt = i;s += 1;}}int st = (1 << s);vector<vector<int> > dp(tot, vector<int>(st, INF));s = 0;for(int i=0;i<tot;i++) {if(a[i] == 0) {dp[i][1 << s] = 0;s += 1;}}// first记录最小值, second记录当前所在的点的位置priority_queue<pair<int, pair<int, int> >, vector<pair<int, pair<int, int> > >, greater<> > q;// first记录点信息, second记录所选点的状态vector<vector<pair<int, int> > > pre(tot, vector<pair<int, int>>(st));vector<vector<int> > flg(n, vector<int>(m));vector<int> xx = {1, 0, -1, 0};vector<int> yy = {0, 1, 0, -1};function<void(int, int)> Dfs = [&](int root, int state) {if(pre[root][state].second == 0) {return;}flg[root / m][root % m] = 1;// 与前面的状态转移正好是相反的if(pre[root][state].first == root) {Dfs(root, state ^ pre[root][state].second);}Dfs(pre[root][state].first, pre[root][state].second);};function<void(int)> dijkstra = [&](int state) {vector<bool> vis(tot);while(!q.empty()) {auto u = q.top();q.pop();int s1 = u.second.first * m + u.second.second;if(vis[s1]) {continue;}vis[s1] = true;for(int i=0;i<4;i++) {int dx = u.second.first + xx[i];int dy = u.second.second + yy[i];int s2 = dx * m + dy;if(dx >= 0 && dx < n && dy >= 0 && dy < m && dp[s2][state] > dp[s1][state] + a[s2]) {dp[s2][state] = dp[s1][state] + a[s2];// 记录前一个状态pre[s2][state] = make_pair(s1, state);q.emplace(dp[s2][state], make_pair(dx, dy));}}}};for(int k=1;k<st;k++) {for(int i=0;i<tot;i++) {for(int sub=k;sub;sub=(sub-1)&k) {int tmp = dp[i][sub] + dp[i][k ^ sub] - a[i];if(dp[i][k] > tmp) {dp[i][k] = tmp;// 记录前一个状态pre[i][k] = make_pair(i, sub);}}if(dp[i][k] != INF) {q.emplace(dp[i][k], make_pair(i / m, i % m));}}dijkstra(k);}// 特判没景点的情况if(rt == -1) {rt = 0;dp[rt][st - 1] = 0;}cout << dp[rt][st - 1] << '\n';Dfs(rt, st - 1);for(int i=0;i<n;i++) {for(int j=0;j<m;j++) {if(a[i * m + j] == 0) {cout << 'x';} else {cout << (flg[i][j] == 1 ? 'o' : '_');}}cout << '\n';}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/203466.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#无标题栏窗体拖动方法

在C#中&#xff0c;可以通过以下步骤实现无标题栏窗体拖动功能&#xff1a; 设置窗体的BorderStyle属性为None&#xff0c;这将隐藏窗体的标题栏和边框。 在窗体的MouseDown事件中&#xff0c;记录鼠标按下时的坐标。 在窗体的MouseMove事件中&#xff0c;计算鼠标移动的偏移…

【桑基图】绘制桑基图

绘制桑基图 一、绘制桑基图&#xff08;1&#xff09;方法一&#xff1a;去在线网站直接绘制&#xff08;2&#xff09;方法二&#xff1a;写html之后在vscode上运行 二、遇到的问题&#xff08;1&#xff09;当导入一些excel的时候&#xff0c;无法绘制出桑基图 一、绘制桑基图…

用23种设计模式打造一个cocos creator的游戏框架----(三)外观模式模式

1、模式标准 模式名称&#xff1a;外观模式 模式分类&#xff1a;结构型 模式意图&#xff1a;为一组复杂的子系统提供了一个统一的简单接口。这个统一接口位于所有子系统之上&#xff0c;使用户可以更方便地使用整个系统。 结构图&#xff1a; 适用于&#xff1a; 当你想为…

Nginx的安装、升级和管理

目录 一. nginx介绍 1. nginx简介 2. nginx和apache区别 二. nginx编译安装 1. 下载解压nginx安装包&#xff0c;并安装nginx依赖包 2. 创建运行用户和组 3. 编译安装并补全 4. 效验结果 三. 平滑升级nginx 1. 下载解压nginx安装包 2. 编译安装 3. 替换二进制文件 …

SpringMvc入坑系列(一)----maven插件启动tomcat

springboot傻瓜式教程用久了&#xff0c;回过来研究下SSM的工作流程&#xff0c;当然从Spring MVC开始&#xff0c;从傻瓜式入门处理请求和页面交互&#xff0c;再到后面深入源码分析。 本人写了一年多的后端和半年多的前端了。用的都是springbioot和vue&#xff0c;源码一直来…

机器学习实验六:聚类

系列文章目录 机器学习实验一&#xff1a;线性回归机器学习实验二&#xff1a;决策树模型机器学习实验三&#xff1a;支持向量机模型机器学习实验四&#xff1a;贝叶斯分类器机器学习实验五&#xff1a;集成学习机器学习实验六&#xff1a;聚类 文章目录 系列文章目录一、实验…

持续集成交付CICD: Sonarqube REST API 查找与新增项目

目录 一、实验 1.SonarQube REST API 查找项目 2.SonarQube REST API 新增项目 一、实验 1.SonarQube REST API 查找项目 &#xff08;1&#xff09;Postman测试 转换成cURL代码 &#xff08;2&#xff09;Jenkins添加凭证 &#xff08;3&#xff09;修改流水线 pipeline…

node切换版本

可打开黑窗口来进行命令输入操作&#xff1a; 1. node -v &#xff1a;查看当前版本 2.nvm list :查看已经下载的版本 3.nvm list available查看可用的node.js版本号&#xff1a; 4.nvm install node版本号(例如&#xff1a;nvm install 12.17.0)即可安装对应版本以及自动安装…

pgsql 判空并设置默认值

在 PostgreSQL 中&#xff0c;可以使用 COALESCE 函数来判断值是否为空并设置一个默认值。 例如&#xff0c;假设有一个表格 users&#xff0c;其中有一个列 username。如果 username 为空&#xff0c;则设置默认值为 ‘guest’&#xff0c;可以使用以下查询&#xff1a; SEL…

6-tornado配置文件的使用(命令行解析、文件设置)

tornado.options options 可以让服务运行前提前设置参数&#xff0c;而常见的2种设置参数方式为&#xff1a;1. 命令行设置 2. 文件设置命令行解析 使用tornado.options.define前定义&#xff0c;通常在模块的顶层。 然后&#xff0c;可以将这些选项作为以下属性的属性进行访…

损失函数(目标函数)

损失函数&#xff08;目标函数&#xff09;是用来衡量模型的预测值与实际值之间差异的函数。对于线性回归问题&#xff0c;最常用的损失函数是平方误差损失函数&#xff0c;也称为均方误差&#xff08;Mean Squared Error, MSE&#xff09;。 平方误差损失函数的形式是&#x…

某60内网渗透之跨平台横向移动【windows计划任务利用】

内网渗透 文章目录 内网渗透跨平台横向移动【windows计划任务利用】实验目的实验环境实验工具实验原理实验内容跨平台横向移动[windows计划任务利用] 实验步骤针对 WindowsXP/2003 的利用方式(at命令)针对 Windows Vista 及以上版本的利用方式(schtasks命令)跨平台横向移动…

轻快小miniconda3在linux下的安装配置-centos9stream-Miniconda3 Linux 64-bit

miniconda与anaconda的区别&#xff1a; Miniconda 和 Anaconda 是用于管理环境和安装软件包的 Python 发行版。它们之间的主要区别在于以下几点&#xff1a; 1. 安装内容和大小&#xff1a; Anaconda&#xff1a; Anaconda 是一个完整的 Python 数据科学平台&#xff0c;包含…

easyui实现省市县三级联动

一、技术: 前端采用的是easyui+jquery+jsp页面 后端采用springmvc+mybatis+mysql8 效果图 二、cascadeEasyui.jsp页面 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%String path = request.getContex…

wait notify

文章目录 1. API 介绍2. 怎么使用wait、notify2.1 sleep 和 wait 的区别2.2 sleep 和 wait 的使用模板 1. API 介绍 都属于 Object 对象的方法。必须获得此对象的锁&#xff0c;才能调用这几个方法&#xff0c;只有重量级锁才能调用wait、notify obj.wait() 让进入 object 监…

vue中实现数字+英文字母组合键盘

完整代码 <template><div class"login"><div click"setFileClick">欢迎使用员工自助终端</div><el-dialog title"初始化设置文件打印消耗品配置密码" :visible.sync"dialogSetFile" width"600px&quo…

持续集成交付CICD:Sonarqube自动更新项目质量配置

目录 一、实验 1.Sonarqube手动自定义质量规则并指定项目 2.Sonarqube自动更新项目质量配置 一、实验 1.Sonarqube手动自定义质量规则并指定项目 &#xff08;1&#xff09;自定义质量规则 ①新配置 ②更多激活规则③根据需求激活相应规则④已新增配置 ⑤ 查看 &#x…

Hive数据库系列--Hive数据类型/Hive字段类型/Hive类型转换

文章目录 一、Hive数据类型1.1、数值类型1.2、字符类型1.3、日期时间类型1.4、其他类型1.5、集合数据类型1.5.1、Struct举例1.5.2、Array举例1.5.3、Map举例 二、数据类型转换2.1、隐式转换2.2、显示转换 本章主要讲解hive的数据类、字段类型。官网文档地址见https://cwiki.apa…

二百一十三、Flume——Flume拓扑结构介绍

一、目的 最近在看尚硅谷的Flume资料&#xff0c;看到拓扑结构这一块&#xff0c;觉得蛮有意思&#xff0c;于是整理一下Flume的4种拓扑结构 二、拓扑结构 &#xff08;一&#xff09;简单串联 1、结构含义 这种模式是将多个flume顺序连接起来了&#xff0c;从最初的sourc…

【数据结构】- 详解哈夫曼树(用 C 语言实现哈夫曼树的构造和哈夫曼编码)

目录 一、哈夫曼树的基本概念 二、哈夫曼树的构造算法 2.1 - 哈夫曼树的构造过程 2.2 - 哈夫曼树的存储表示 2.3 - 算法实现 三、哈夫曼编码 3.1 - 哈夫曼编码的主要思想 3.2 - 哈夫曼编码的性质 3.3 - 算法实现 一、哈夫曼树的基本概念 哈夫曼树的定义&#xff0c;涉…