传送门

Description
作为公司老板的你手下有N个员工,其中有M个特殊员工。现在,你有一个消息需要传递给你的特殊员工。因为你的公司业务非常紧张,所以你和员工之间以及员工之间传递消息会造成损失。因此,你希望只告诉一部分特殊员工,然后依靠员工之间传递消息,使得所有的特殊员工都能获得要传递的消息,同时使得损失最小。同时,你不关心要传递的消息是否经过了其它员工。求最小的损失。
Constraint
补全右侧代码区中的int solve(int N, vector cost_e, vector employees, vector cost_b)函数,完成挑战任务中提出的要求:返回最小的损失。
如果需要,你可以在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)
Input
N = 3;
cost_e = {{1, 2, 2}, {1, 3, 2}, {2, 3, 2}};
employees = {1, 2};
cost_b = {1, 1000};
Output
3

题意
给出n个点的完全图,另外还有一个点,向其中的m个点连有向边。求至少包含这个点和m个点的最小连通图,并输出最小的边权和。
分析
首先,问题是求最小边集,边的有向无向其实不重要,所以从单独的点连向m个点的那些有向边,可以直接看成无向边,因此单独的点和那m个点是完全相同的,不用再单独考虑;
问题转化为:已知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。

最新文章

  1. iOS 相机
  2. NGUI 指定视口大小
  3. 照着别人的demo自己试着做了个放大镜效果
  4. LIST_ENTRY
  5. ASCII码排序
  6. android调用系统相机拍照并保存在本地
  7. iOS 画图讲解
  8. Part 13 Cast and Convert functions in SQL Server
  9. 不要将缓存服务器与Tomcat放在单台机器上,否则出现竞争内存问题
  10. 更新Android SDK 出错 Failed to rename directory \temp\ToolPackage.old01
  11. Ppoj 1014 深搜
  12. Django学习-1-管理我的django程序
  13. spring boot多环境配置 直接上代码
  14. 【读英文文档】Whetting Your Appetite(刺激你的食欲)
  15. Linux-Centon7安装以及配置
  16. 后台返回路由的数组,然后根事先写好的路由比对如果相等就放到一个数组中https://www.cnblogs.com/zhengrunlin/p/8981017.html
  17. Java常考面试题(一)
  18. quick3.3final版创建项目报错解决
  19. Root :: AOAPC I: Beginning Algorithm Contests (Rujia Liu) Volume 5. Dynamic Programming
  20. [转]Mongodb的下载和安装

热门文章

  1. yolo算法框架使用二
  2. mysql 分页查询时,如何正确的获取总数
  3. Linux下的消息队列
  4. 接口测试中抓包工具Charles的使用
  5. 统计学习方法c++实现之八 EM算法与高斯混合模型
  6. redis-4.0.2
  7. 前端常见算法面试题之 - 从尾到头打印链表[JavaScript解法]
  8. 013-- mysql常用的查询优化方法
  9. 高可用OpenStack(Queen版)集群-12.Cinder计算节点
  10. Erlang运行时中的无锁队列及其在异步线程中的应用