传送门

斯坦纳树

给一个联通图,求 $k$ 个关键点联通的最小生成树权值

设 $f[o][i]$ 表示当前关键点选择状态为 $o$ ,以点 $i$ 为根的树的最小权值

初始 $f[1<<(i-1)][i]=val[i]$ ,$val[i]$ 表示点 $i$ 的权值

那么从小到大枚举状态 $o$

对于每一个状态枚举 $o$ 的真子集 $op$,

则 $f[o][i]=min(f[o][i],f[o-op][i]+f[op][i]-val[i])$ 注意代价要减去 $val[i]$ ,因为两个状态合并时点 $i$ 的代价会算两次

这样转移还不够,还要考虑一个树自己扩展出去

所以枚举与根 $i$ 相连的点 $v$

则 $f[o][v]=min(f[o][v],f[o][i]+val[v])$ ,这样dp的顺序不好确定,但是发现这个很像 SPFA 的式子,所以用 SPFA 来进行转移

总结一下,对于每个状态,先考虑树的合并,再考虑树的扩展

至于为什么这样做是对的呢:

感性理解一下,这样显然会考虑到所有的情况,所以是对的2333....

SPFA时以 $f[o][i]!=INF$ 为起点

因为此题要输出路径,所以维护一个 $fa[o][i]$ 存状态 $o,i$ 是从哪两个子树合并的,对于扩展的子树就特殊处理一下

骚操作:枚举一个状态的真子集 : $for(int op=(o-1)&o;op;op=(op-1)&o)$

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
inline int read()
{
int x=,f=; char ch=getchar();
while(ch<''||ch>'') { if(ch=='-') f=-; ch=getchar(); }
while(ch>=''&&ch<='') { x=(x<<)+(x<<)+(ch^); ch=getchar(); }
return x*f;
}
const int N=,M=,INF=1e9+;
int fir[M],from[M<<],to[M<<],cntt;
void add(int &a,int &b)
{
from[++cntt]=fir[a]; fir[a]=cntt;
to[cntt]=b;
}
int n,m,K,tot;
int val[M],pos[N],id[N][N];
int f[M][M];
bool inq[M],mp[N][N];
struct path {
int o1,x1,o2,x2;
}fa[M][M];
void SPFA(int p)
{
queue <int> q;
for(int i=;i<=tot;i++) if(f[p][i]<INF) q.push(i),inq[i]=;
while(!q.empty())
{
int x=q.front(); q.pop(); inq[x]=;
for(int i=fir[x];i;i=from[i])
{
int &v=to[i];
if(f[p][v]>f[p][x]+val[v])
{
f[p][v]=f[p][x]+val[v];
fa[p][v]=(path){p,x,-,v};//扩展的子树特殊处理成-1
if(!inq[v]) q.push(v),inq[v]=;
}
}
}
}
void dfs(int o,int x)
{
int o1=fa[o][x].o1,x1=fa[o][x].x1,o2=fa[o][x].o2,x2=fa[o][x].x2;
if(!(o1|x1|o2|x2)) return;
dfs(o1,x1);
if(o2==-) mp[(x2-)/m+][(x2-)%m+]=;//如果是-1则说明此点有志愿者
else dfs(o2,x2);//否则向下一个子树转移
}
int main()
{
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
memset(f,0x3f,sizeof(f));
n=read(),m=read(); tot=n*m;
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
{
id[i][j]=(i-)*m+j;//把点缩起来
val[id[i][j]]=read();
if(!val[id[i][j]]) pos[++K]=id[i][j];
if(j>) add(id[i][j-],id[i][j]),add(id[i][j],id[i][j-]);
if(i>) add(id[i-][j],id[i][j]),add(id[i][j],id[i-][j]);
}
int mx=(<<K)-;
for(int i=;i<=K;i++) f[<<i-][pos[i]]=;
SPFA();
for(int o=;o<=mx;o++)
{
for(int j=;j<=tot;j++)
for(int op=(o-)&o;op;op=(op-)&o)
if(f[o][j]>f[o^op][j]+f[op][j]-val[j])
{
f[o][j]=f[o^op][j]+f[op][j]-val[j];
fa[o][j]=(path){o-op,j,op,j};
}
SPFA(o);
}
int ans=INF,rt=;
for(int i=;i<=tot;i++) if(f[mx][i]<ans) ans=f[mx][i],rt=i;
dfs(mx,rt);
printf("%d\n",ans);
for(int i=;i<=n;i++)
{
for(int j=;j<=m;j++)
{
if(!val[id[i][j]]) printf("x");
else if(mp[i][j]) printf("o");
else printf("_");
}
printf("\n");
}
return ;
}

P4294 [WC2008]游览计划的更多相关文章

  1. bzoj2595 / P4294 [WC2008]游览计划

    P4294 [WC2008]游览计划 斯坦纳树 斯坦纳树,是一种神奇的树.它支持在一个连通图上求包含若干个选定点的最小生成树. 前置算法:spfa+状压dp+dfs(大雾) 我们设$f[o][P]$为 ...

  2. luogu P4294 [WC2008]游览计划

    LINK:游览计划 斯坦纳树例题. 斯坦纳树是这样一类问题:带权无向图上有K个关键点 求出包含这K个点的最小生成树. 也就是说 求最小生成树 但是 并不是整张图 仅限于K个点. 可以发现我们利用克鲁斯 ...

  3. 洛谷 P4294 [WC2008]游览计划

    题目链接 不是很会呢,但似乎抄了题解后有点明白了 sol:状态DP显然,其实是要构建一棵最小生成树一样的东西,我自己的理解(可能不是很对哦希望多多指教)f[x][y][zt]就是到x,y这个点,状态为 ...

  4. P4294 [WC2008]游览计划 (斯坦纳树)

    题目链接 差不多是斯坦纳树裸题,不过边权化成了点权,这样在合并两棵子树时需要去掉根结点的权值,防止重复. 题目还要求输出解,只要在转移时记录下路径,然后dfs一遍就好了. #include<bi ...

  5. BZOJ_2595_[Wc2008]游览计划_斯坦纳树

    BZOJ_2595_[Wc2008]游览计划_斯坦纳树 题意: 分析: 斯坦纳树裸题,有几个需要注意的地方 给出矩阵,不用自己建图,但枚举子集转移时会算两遍,需要减去当前点的权值 方案记录比较麻烦,两 ...

  6. [WC2008]游览计划 解题报告

    [WC2008]游览计划 斯坦纳树板子题,其实就是状压dp 令\(dp_{i,s}\)表示任意点\(i\)联通关键点集合\(s\)的最小代价 然后有转移 \[ dp_{i,S}=\min_{T\in ...

  7. 【BZOJ2595】 [Wc2008]游览计划

    BZOJ2595 [Wc2008]游览计划 Solution 考虑这是一个最小费用连通性的问题,既然大家都说这是什么斯坦纳树那就是的吧... 所以我们肯定可以这样设一个dp状态: \(dp_{i,j, ...

  8. 【BZOJ2595】[Wc2008]游览计划 斯坦纳树

    [BZOJ2595][Wc2008]游览计划 Description Input 第一行有两个整数,N和 M,描述方块的数目. 接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该方块为 ...

  9. 【LG4294】[WC2008]游览计划

    [LG4294][WC2008]游览计划 题面 洛谷 bzoj 题解 斯坦纳树板子题. 斯坦纳树的总结先留个坑. 代码 #include <iostream> #include <c ...

随机推荐

  1. HDU 5293 Tree chain problem

    树状数组 + dp 设$f_i$表示以$i$为根的子树中的能选取的最大和,$sum_x$表示$\sum_{f_y}$  ($y$是$x$的一个儿子),这样子我们把所有给出的链按照两点的$lca$分组, ...

  2. 面向对象property属性、静态方法和类方法

    一.property属性 1.什么是property特性? property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值. from math import pi class Circl ...

  3. 专题2-通过按键玩中断\第1课-中断处理流程深度剖析-lesson1

    中断概念 1.中断生命周期 串口先产生一个事件,该事件传送到中断控制器里面,中断控制器会进行相应过滤,能通过过滤,那么就交给CPU去处理. 2.中断源 2440芯片手册 6410芯片手册 3.中断过滤 ...

  4. MSGPACK和PROTOBUF的故事(MSGPACK明显生产力不足)

    作者曾经在2014年测试出MSGPACK的关键字和中文字符有很大的冲突,所以后来放弃了,本文为很多年前写的一个对比,后来我们一直在使用HTTP协议和PROTOBUF. 看看MSGPACK的文档,自称效 ...

  5. javascript中把一个数组的内容全部赋值给另外一个数组

    如:var a = [1,2,3,4];var b= [];b = a;这个不是把值赋值过去而是b作为a的引用,b改变的是a如何b指向的是一个新数组,a把元素值全部赋值过去? 1.普通数组可以使用   ...

  6. 【Web API2】ASP.NET Web API Security

    实现安全的方式既可以是host提供,也可以框架提供. 1,HTTP Module 方式,工作在IIS上,所以web api要托管在IIS上才行.其作用于HTTP管道的最前端,所以这种方式影响的是全局, ...

  7. Eclipse下Android的NDK开发环境配置

    编辑2016年7月26日——增加了下载网址,修改了一些错误. 摸索了一周,走了很多弯路,磕磕绊绊,总算是弄好了NDK的开发环境,在这里总结一下吧. 一.Android NDK开发环境 首先下载安装JR ...

  8. 使用Recyclerview实现图片水平自动循环滚动

    简介: 本篇博客主要介绍的是如何使用RecyclerView实现图片水平方向自动循环(跑马灯效果) 效果图: 思路: 1.准备m张图片 1.使用Recyclerview实现,返回无数个(实际Inter ...

  9. android library使用方法

    一.Android library使用情景 通用模块的重复使用,项目做多了,其实都是差不多,核心模块基本无需大的改动,需要改的只是核心模块上的业务功能而已. Java中可以打包成库,或者说,单纯的ja ...

  10. 小议C#接口的隐式与显示实现(续)

    上文连接,讲的比较模糊,而且调用起来感觉比较混乱 http://www.cnblogs.com/walleyekneel/p/3581489.html 这次改为显式接口调用,可能项目也有这个一个需求 ...