传送门

斯坦纳树

给一个联通图,求 $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. c语言实践打印字母三角形

    效果如下: int main(void) { char ch = 'A';//当前要打印的字符 ;//每行要打印字符的个数 ; i < ; i++,count++) { ; j < cou ...

  2. Luogu 3957 [NOIP2017]普及组 跳房子

    写了好久,感觉自己好菜,唉…… 首先发现这个$g$的取值具有单调性,可以想到二分答案,然后考虑用$dp$来检验,这样子可以写出朴素的转移方程: 设$f_i$表示以$i$结尾的最大价值,那么有$f_i ...

  3. hdu 4678 Mine

    HDU 4678 把点开空地时会打开的一大片区域看成一块,题目中说到,在一盘游戏 中,一个格子不可能被翻开两次,说明任意两块空地不会包含相同的格子. 那么就可以看成一个组合游戏. 当空地旁边没连任何数 ...

  4. HTTP文件上传插件开发文档-JSP

    版权所有 2009-2016 荆门泽优软件有限公司 保留所有权利 官方网站:http://www.ncmem.com/ 产品首页:http://www.ncmem.com/webplug/http-u ...

  5. TensorFlow安装教程

    Windows7 安装TensorFlow(本人试了好多方法后的成果):https://www.cnblogs.com/bxyan/p/6869237.html Linux: sudo pip ins ...

  6. OpenSSH/PuTTY/SSH使用

    OpenSSH/PuTTY/SSH 常用SSH服务指令 ① 启动SSH服务的命令 service sshd start ② 停止SSH服务的命令 service sshd stop ③ 重新启动SSH ...

  7. 平方十位数——第八届蓝桥杯JavaB组(国赛)第一题

    原创 标题:平方十位数 由0~9这10个数字不重复.不遗漏,可以组成很多10位数字.这其中也有很多恰好是平方数(是某个数的平方). 比如:1026753849,就是其中最小的一个平方数. 请你找出其中 ...

  8. 20145218张晓涵_Exp5 MSF基础应用

    20145218张晓涵_Exp5 MSF基础应用 实验原理 MS08-067漏洞描述 MS08-067漏洞的全称为"Windows Server服务RPC请求缓冲区溢出漏洞",如果 ...

  9. Nginx开发HTTP模块入门

    Nginx开发HTTP模块入门 我们以一个最简单的Hello World模块为例,学习Nginx的模块编写.假设我们的模块在nginx配置文件中的指令名称为hello_world,那我们就可以在ngi ...

  10. iOS No suitable application records were found. Verify your bundle identifier 'xxx' is correct.

    1.错误提示 打包工程,Validate 的时候提示: No suitable application records were found. Verify your bundle identifie ...