绿色计算大赛决赛 第二阶段 消息传递(斯坦纳树 状压dp+spfa)
如果需要,你可以在solve函数外添加其它代码,但是不要改变Solver类的名字以及solve函数的形式,也不要改变DeliveryCost类的定义。
函数参数说明如下:
- int N:员工个数(2 <= N <= 50),员工编号从1到N;
- vector<DeliveryCost> cost_e:员工之间传递消息的损失,员工cost_e[i].u和cost_e[i].v之间传递消息的损失为cost_e[i].cost。数据保证任意两个员工之间传递消息的损失只出现一次,整个数组长度为N(N-1)/2。(1 <= cost <= 1000)
- vector<int> employees:特殊员工的编号,个数为M(1 <= M <= 10);
- vector<int> cost_b:你传递给每个特殊员工的损失,与employees一一对应。(1 <= cost <= 1000)
cost_e = {{1, 2, 2}, {1, 3, 2}, {2, 3, 2}};
employees = {1, 2};
cost_b = {1, 1000};
问题转化为:已知N个点M条无向带权边,求一个最小连通图,必须包含其中的K个特殊点。因为要边权的花费最小,所以图中是不应该出现环的,最小连通图一定是一棵树。具有这样性质的树,被定义为斯坦纳树。
斯坦纳树的求解貌似是一个NP问题,做法基本还是暴力,但是因为这道题数据量很小,所以是可做的。推荐一篇大佬分析斯坦纳树的博客。
因为特殊点最多只有11个,所以暴力搜可以从K入手,先大致生成K个点的一个生成树,再看能不能借用其他N-K个点形成的网络的一部分来降低花费。
具体地来说,把状态定义为$$$(i, (a_1a_2...a_k)_{bin})$$$,表示以$$$i$$$号节点为根的一棵树,$$$a_j$$$为1则表示$$$i$$$至少与第$$$j$$$个特殊点是连通的。
现在用$$$state$$$简记$$$(a_1a_2...a_k)_{bin}$$$,那么$$$dp[i][state]$$$记录状态$$$(i,state)$$$的最小花费,有下面两个转移方程:
$$$$$$
\begin{align}
& dp[i][state]=min_{substate\subset state}\{dp[i][substate]+dp[i][state-substate]\} \\
& dp[i][state]=min\{dp[i][state],dp[j][state]+w(i,j)\}
\end{align}
$$$$$$
对这块理解不是很到位,以下可能有不严谨之处。
第一层转移,对于固定的$$$i$$$,大的$$$state$$$的$$$dp$$$用$$$substate$$$的dp求出并取最小,相当于用两棵小的树可以合并成一棵大一点的树。
然后第二层转移,所有的生成树大致长什么样都知道了,但很可能还不是最优的,还要进一步减少花费。转移方程的形式其实很像求最短路的形式,对于固定的$$$state$$$,相当于把它们合并为一个新的点,并且就以这个点为起点,在新的图上跑一次最短路,也就是借用其他的点$$$j$$$对原来的边进行了松弛。
搜索完所有的状态以后,任意一个特殊点$$$X$$$,则$$$dp[X][{1...1}]$$$就是我们要求的斯坦纳树的最小花费。
比赛的时候想到的错误算法:
- 网络流:
- 从单独的点出发->特殊点, 特殊点<->其他点, 特殊点->汇点)。
- 错误原因:正解的流出量可以大于流入量,导致费用大的边也被选择,或者一条边对答案贡献多次。
- 缩边
- 找K个特殊点,两两之间的最短路,缩为一条边,构造新的图,求K个点的最小生成树
- 错误原因:特殊点之间的最短路可以分叉,缩边会对分叉前的公共部分重复计算。
#include <stdio.h>
#include<queue>
#include<vector>
#include <memory.h>
using std::queue;
using std::vector;
/*
*dp:
*dp[i][st] 包含第i个点,且至少和state为1的关键点相连的最小花费
*转移
*dp[i][st]=Min{dp[i][st],dp[i][st-sub]+dp[i][sub]} 分解为两个
*dp[i][st]=Min{dp[i][st],dp[j][st]+w(i,j)} i和j有边,关键点外面的部分spfa一下
*/
#define INF 0x3f3f3f3f
#define maxn 55
int g[maxn][maxn];
int dp[maxn][ << ];
queue<int> help;
int N,K;
int vis[maxn];
struct DeliveryCost {
int u;
int v;
int cost;
};
void spfa(int cs){
while(!help.empty()) {
int id = help.front();help.pop();
vis[id] = ;
for(int i=;i<=N;++i){
if (id == i || g[id][i] == INF)continue;
if(dp[i][cs]>dp[id][cs]+g[id][i]){
dp[i][cs] = dp[id][cs] + g[id][i];
if(!vis[i]){
vis[i] = ; help.push(i);
}
}
}
}
} int solve(int n,
vector<DeliveryCost> cost_e,
vector<int> employees,
vector<int> cost_b) {
/*********begin*********/
memset(g, 0x3f, sizeof g);
memset(dp, 0x3f, sizeof dp);
//建图
//员工到员工
int sz = cost_e.size();
int tu, tv, tc;
for(int i=;i<sz;i++) {
tu = cost_e[i].u; tv = cost_e[i].v; tc = cost_e[i].cost;
g[tu][tv] = tc;
g[tv][tu] = tc;
}
//老板到特殊员工
K = cost_b.size();
for(int i=;i<K;i++) {
g[n + ][employees[i]] = cost_b[i];
g[employees[i]][n+] = cost_b[i];
dp[employees[i]][ << i] = ;
}
dp[n + ][ << K] = ;
K++; N=n+;
int limit = ( << K) - ;
//第一层转移
for(int sta=;sta<=limit;sta++) {//遍历state
for(int i=;i<=N;i++) {
for(int s=sta;s;s=(s-)&sta) //遍历substate
if(dp[i][s]+dp[i][sta-s]<dp[i][sta])
dp[i][sta] = dp[i][s] + dp[i][sta - s];
if (dp[i][sta] < INF) {//i-sta被松弛,放入队列
help.push(i);
vis[i] = ;
}
}
//第二层转移
spfa(sta);
}
//N是特殊点中的一个
return dp[N][limit];
/*********end*********/ }
int main(){
//测试一下样例
int n = ;
vector<DeliveryCost>cost_e{ { , , },{ , , },{ , , } };
vector<int> employees{ , };
vector<int> cost_b{ , };
printf("%d",solve(n, cost_e, employees, cost_b));
}
以每个点为根都有$$$2^k$$$个$$$state$$$,求解每一个都遍历了其所有$$$substate$$$。含$$$x$$$个$$$1$$$的state共$$$C_k^x$$$个,$$$substate$$$数量都是$$$2^x$$$,所以复杂度为
$$$$$$\begin{align}
& n\cdot\sum{C_k^x\cdot 2^x}\\
=& n\cdot\sum{C_k^x\cdot 1^{k-x}\cdot 2^x}\\
=& (1+2)^kn =3^kn
\end{align}$$$$$$
此外,对每个$$$state$$$,都要跑一遍$$$spfa$$$,因为是稠密图,如果按spfa的最坏情况来看就是:
$$$$$$\begin{align}
& 2^k\cdot O(spfa)\\
=& 2^k\cdot O(VE)\\
=& 2^k\cdot O(n\cdot \frac{n(n-1)}{2})\\
=& 2^k\cdot O(n^3)\\
\end{align}$$$$$$
最终复杂度为$$$O(3^kn+2^kn^3)=O(2^kn^3)$$$,大约在3e8左右,但是实际上很快,十组样例只跑了14.408秒,可能是因为$$$spfa$$$的复杂度只有$$$O(kE)$$$吧,也有可能是因为数据比较水233。
绿色计算大赛决赛 第二阶段 消息传递(斯坦纳树 状压dp+spfa)的更多相关文章
- 【bzoj4006】[JLOI2015]管道连接 斯坦纳树+状压dp
题目描述 给出一张 $n$ 个点 $m$ 条边的无向图和 $p$ 个特殊点,每个特殊点有一个颜色.要求选出若干条边,使得颜色相同的特殊点在同一个连通块内.输出最小边权和. 输入 第一行包含三个整数 n ...
- bzoj 4006 [JLOI2015]管道连接(斯坦纳树+状压DP)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=4006 [题意] 给定n点m边的图,连接边(u,v)需要花费w,问满足使k个点中同颜色的 ...
- BZOJ4006: [JLOI2015]管道连接(斯坦纳树,状压DP)
Time Limit: 30 Sec Memory Limit: 128 MBSubmit: 1171 Solved: 639[Submit][Status][Discuss] Descripti ...
- BZOJ2595: [Wc2008]游览计划(斯坦纳树,状压DP)
Time Limit: 10 Sec Memory Limit: 256 MBSec Special JudgeSubmit: 2030 Solved: 986[Submit][Status][ ...
- bzoj1402 Ticket to Ride 斯坦纳树 + 状压dp
给定\(n\)个点,\(m\)条边的带权无向图 选出一些边,使得\(4\)对点之间可达,询问权值最小为多少 \(n \leqslant 30, m \leqslant 1000\) 首先看数据范围,\ ...
- bzoj 4006 管道连接 —— 斯坦纳树+状压DP
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4006 用斯坦纳树求出所有关键点的各种连通情况的代价,把这个作为状压(压的是集合选择情况)的初 ...
- 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] ...
- hdu4085 Peach Blossom Spring 斯坦纳树,状态dp
(1)集合中元素表示(1<<i), i从0开始 (2)注意dp[i][ss] = min(dp[i][ss], dp[i][rr | s[i]] + dp[i][(ss ^ rr) | s ...
- 【BZOJ 2595】2595: [Wc2008]游览计划 (状压DP+spfa,斯坦纳树?)
2595: [Wc2008]游览计划 Time Limit: 10 Sec Memory Limit: 256 MBSec Special JudgeSubmit: 1572 Solved: 7 ...
随机推荐
- tcp三次握手四次挥手那些事
建立TCP需要三次握手才能建立,而断开连接则需要四次挥手.三次握手,四次挥手流程图如下: 一.首先看下如何通过三次挥手----------建立连接 首先客户端发送连接请求报文,服务端接受连接后回复AC ...
- Spring学习(十五)----- Spring AOP通知实例 – Advice
Spring AOP(面向方面编程)框架,用于在模块化方面的横切关注点.简单得说,它只是一个拦截器拦截一些过程,例如,当一个方法执行,Spring AOP 可以劫持一个执行的方法,在方法执行之前或之后 ...
- 【转载】钉钉开发c#帮助类 获取用户信息 DingHelper.cs
using System;using System.Collections.Generic;using System.Configuration;using System.Linq;using Sys ...
- 《Postgre SQL 即学即用 (第三版)》 分享 pdf下载
链接:https://pan.baidu.com/s/1akR33VqEkt99UqJUfiy2OA提取码:3p1k
- [Lua] 迭代器 闭合函数 与 泛型for
首先看看一下闭合函数(closure),见如下代码: function newCounter() local i = 0 -- 非局部变量(non-local variable) return fun ...
- Laya 1.x 按文件夹TS代码合并
Laya 1.x 使用TS开发时,经常会碰到代码文件太多,加载index.html时时间太长的问题.Laya编辑器貌似没有自带JS代码合并的功能.基于Laya去实现JS合并需要修改编辑器源码,合并JS ...
- canvas反向裁剪技巧
我们都知道在canvas 可以通过clip来实现剪裁功能,其步骤一般是先设置要裁剪的区域(路径),然后通过ctx.clip()的实现裁剪,裁剪之后,后续的绘制只能在裁剪的区域显示效果,比如如下一段代码 ...
- VGGnet——从TFrecords制作到网络训练
作为一个小白中的小白,多折腾总是有好处的,看了入门书和往上一些教程,很多TF的教程都是从MNIST数据集入手教小白入TF的大门,都是直接import MNIST,然后直接构建网络,定义loss和opt ...
- 使用proxyee-down解决百度云下载限速问题
1.在下面页面安装HTTP下载器 https://github.com/proxyee-down-org/proxyee-down#%E4%B8%8B%E8%BD%BD 2.安装switchy插件 h ...
- 前端基础css
CSS主要学习的是选择器和样式属性. 导入css的方式:行内样式,内部样式,外部样式(推荐使用) 行内样式:在标记的style属性中设定CSS样式 <p style="color: g ...