传送门

斯坦纳树

给一个联通图,求 $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. fail-fast 与 fail-save 机制的区别

    link:https://blog.csdn.net/bigtree_3721/article/details/67095084

  2. SpringBoot26 利用 Ribbon + RestTemplate 调用远程服务资源

    1 RestTemplate扫盲 借助 RestTemplate,Spring应用能够方便地使用REST资源  2 准备 创建三个springCloud项目 >Eureaka : 服务注册中心 ...

  3. linux Shell中常用的条件判断

    linux Shell中常用的条件判断 -b file            若文件存在且是一个块特殊文件,则为真 -c file            若文件存在且是一个字符特殊文件,则为真 -d ...

  4. 模板模式和Comparable类

    模板模式中,父类规定好了一些算法的流程,并且空出一些步骤(方法)留给子类填充 Java的数组类中静态方法sort()就是一个模板,它空出了一个compareTo的方法,留给子类填充,用来规定什么是大于 ...

  5. R 如何 隐藏坐标轴

    x = c(7,5,8)dim(x)<-3names(x)<-c("apple","banana", "cherry")plot ...

  6. Smarty3——内置函数

    Table of Content {$var} {$append} {assign} {block} {call} {config_load} {debug} {extends} {for} {for ...

  7. Part5核心初始化_lesson4---关闭中断

    1.关闭cpsr寄存器里面的I(中断)和F(快速中断)位: 2.设置中断屏蔽寄存器. 针对2440: 这是中断处理过程,当有中断源(没有子中断源)来的时候,它会把这个中断记录在SRCPND里面:它还要 ...

  8. Python开发 第一篇 python的前世今生

    Python前世今生 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC ...

  9. ASP.NET 5 Middleware, Or Where Has My HttpModule Gone?

    31 March 2015 13:22 ASP.NET 5 has been largely rewritten from the ground up, and incorporates some r ...

  10. javascript总结7:算术运算符

    1  运算符: 加号+ 如果是数字类型的变量相加,那么结果为数字类型; 如果是非数字类型的变量相加,结果为字符串类型 2  减号- 如果是非数字类型的变量相减结果为  NaN 3 乘号 * 如果是非数 ...