题目链接

f[i][s]表示以i为根节点,当前关键点的连通状态为s(每个点是否已与i连通)时的最优解。i是枚举得到的根节点,有了根节点就容易DP了。

那么i为根节点时,其状态s的更新为 \(f[i][s]=min\{f[i][s']+f[i][\complement_{s}s']-cost[i]\},s'\in s\)(枚举子集s'后,显然只需要s'的补集。减cost[i]是因为两种状态都包含,cost[i]算重了)

如果我们想合并入当前连通块一个新的非关键点v并以v为根,那么 \(f[v][s]=min\{f[k][s]+cost[v]\},k,v相邻\)

第一个更新可以按顺序,第二个更新没有明显顺序,但是如果固定状态s,很像SPFA,可类似转移。

输出方案,可以每次转移记录转移前的点与状态s,因为可能是转移点也可能是用子集更新。最后随便找一个关键点开始DFS即可。

复杂度 \(O(n\times 3^k+cE\times 2^k)\)

c为SPFA复杂度中的常数,E为边的数量,但几乎达不到全部边的数量,甚至非常小。\(3^k\)来自于子集的转移\(\sum_{i=1}^nC_n^i\times 2^i\),用二项式展开求一下和。

//2080kb	148ms
#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
#define En(i,j) (i*m+j)//Encode
#define De(w) mp(w/m,w%m)//Decode
#define mp std::make_pair
#define pr std::pair<int,int>
const int N=102,INF=1e9,to[5]={1,0,-1,0,1}; int n,m,cost[N],f[(1<<10)+1][N];//换下了顺序 注意!
pr pre[(1<<10)+1][N];
bool inq[N],vis[N];
std::queue<int> q; inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
void SPFA(int *f,pr *pre,int s)
{
while(!q.empty())
{
int now=q.front();
q.pop(), inq[now]=0;
for(int x=now/m,y=now%m,xn,yn,nxt,i=0; i<4; ++i)
if((xn=x+to[i])>=0&&xn<n&&(yn=y+to[i+1])>=0&&yn<m && f[nxt=En(xn,yn)]>f[now]+cost[nxt])
{
f[nxt]=f[now]+cost[nxt], pre[nxt]=mp(now,s);
if(!inq[nxt]) inq[nxt]=1, q.push(nxt);
}
}
}
void DFS(int p,int s)
{
if(!pre[s][p].second) return;//pre.second即 无转移了
vis[p]=1;
if(pre[s][p].first==p) DFS(p,s^pre[s][p].second);
DFS(pre[s][p].first,pre[s][p].second);
} int main()
{
n=read(), m=read(); int K=0, rt=0;
memset(f,0x3f,sizeof f);
for(int tot=0,i=0; i<n; ++i)
for(int j=0; j<m; ++j,++tot)
{
cost[tot]=read();
if(!cost[tot]) f[1<<(K++)][tot]=0, rt=tot;
}
for(int s=1; s<(1<<K); ++s)
{
for(int i=0; i<n*m; ++i)
{
for(int sub=(s-1)&s; sub; sub=(sub-1)&s)
if(f[s][i]>f[sub][i]+f[sub^s][i]-cost[i])
f[s][i]=f[sub][i]+f[sub^s][i]-cost[i], pre[s][i]=mp(i,sub);
if(f[s][i]<INF) q.push(i), inq[i]=1;//多起点,inq[]还是不能省啊
}
SPFA(f[s],pre[s],s);
}
printf("%d\n",f[(1<<K)-1][rt]);
DFS(rt,(1<<K)-1);
for(int i=0,tot=0; i<n; ++i,putchar('\n'))
for(int j=0; j<m; ++j,++tot)
if(!cost[tot]) putchar('x');
else putchar(vis[tot]?'o':'_'); return 0;
}

BZOJ.2595.[WC2008]游览计划(DP 斯坦纳树)的更多相关文章

  1. bzoj 2595 [Wc2008]游览计划(斯坦纳树)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2595 [题意] 给定N*M的长方形,选最少权值和的格子使得要求的K个点连通. [科普] ...

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

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

  3. BZOJ 2595: [Wc2008]游览计划 [DP 状压 斯坦纳树 spfa]【学习笔记】

    传送门 题意:略 论文 <SPFA算法的优化及应用> http://www.cnblogs.com/lazycal/p/bzoj-2595.html 本题的核心就是求斯坦纳树: Stein ...

  4. BZOJ2595 Wc2008 游览计划 【斯坦纳树】【状压DP】*

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

  5. 【BZOJ2595_洛谷4294】[WC2008]游览计划(斯坦纳树_状压DP)

    上个月写的题qwq--突然想写篇博客 题目: 洛谷4294 分析: 斯坦纳树模板题. 简单来说,斯坦纳树问题就是给定一张有边权(或点权)的无向图,要求选若干条边使图中一些选定的点连通(可以经过其他点) ...

  6. BZOJ2595 WC2008游览计划(斯坦纳树)

    斯坦纳树板子题. 考虑状压dp,设f[i][j][S]表示当前在点(i,j)考虑转移,其所在的联通块包含的关键点集(至少)为S的答案. 转移时首先枚举子集,有f[i][j][S]=min{f[i][j ...

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

    [Luogu4294] 题解 : 斯坦纳树 \(dp[i][j]\) 表示以\(i\)号节点为根,当前状态为\(j\)(与\(i\)连通的点为\(1\)) 当根\(i\)不改变时状态转移方程是: \( ...

  8. BZOJ 2595 [Wc2008]游览计划 ——斯坦纳树

    [题目分析] 斯坦纳树=子集DP+SPFA? 用来学习斯坦纳树的模板. 大概就是用二进制来表示树包含的点,然后用跟几点表示树的形态. 更新分为两种,一种是合并两个子集,一种是换根,换根用SPFA迭代即 ...

  9. bzoj:2595: [Wc2008]游览计划

    Description Input 第一行有两个整数,N和 M,描述方块的数目. 接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该方块为一个景点:否则表示控制该方块至少需要的志愿者数 ...

随机推荐

  1. Java基础-线程安全问题汇总

    Java基础-线程安全问题汇总 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.内存泄漏和内存溢出(out of memory)的区别 1>.什么是内存溢出 答:内存溢出指 ...

  2. 微信小程序-点击图片预览

    拿接口  有封装 封装查看另外的博文 点击动作 WXHTML 注意 data-xxxx自定义属性 这样知道我点的是哪个参数

  3. postman断言的几种方式(二)

    1.检查响应体是否包含字符串 pm.test("Body matches string", function () { pm.expect(pm.response.text()). ...

  4. 关于Spring mvc注解中的定时任务的配置

    关于spring mvc注解定时任务配置 简单的记载:避免自己忘记,不是很确定我理解的是否正确.有错误地方望请大家指出. 1,定时方法执行配置: (1)在applicationContext.xml中 ...

  5. shell脚本常用参数

    shell 脚本 常用参数 #!/bin/sh # 在脚本第一行脚本头 # sh为当前系统默认shell,可指定为bash等shell sh -x # 执行过程 sh -n # 检查语法 (a=bbk ...

  6. HDU 3787 A+B 模拟题

    解题报告:就是输入两个用逗号隔开的数字,求出这两个数字的和,并且用正常的方式输出来.直接写一个函数将一个包含逗号的数字转换成十进制的数返回就行了.这里推荐一个函数atoi(),参数是char*型的,然 ...

  7. HDU 1728 逃离迷宫 BFS题

    题目描述:输入一个m*n的地图,地图上有两种点,一种是 . 表示这个点是空地,是可以走的,另一种是 * ,表示是墙,是不能走的,然后输入一个起点和一个终点,另外有一个k输入,现在要你确定能否在转k次弯 ...

  8. Twisted框架

    Twisted是一个事件驱动型的网络模型.时间驱动模型编程是一种范式,这里程序的执行流由外部决定.特点是:包含一个事件循环,当外部事件发生时,使用回调机制来触发相应的处理. 线程模式: 1.单线程同步 ...

  9. python 获取二进制文件

    import requests response = requests.get('https://www.baidu.com/aladdin/img/tools/ip.png')with open(' ...

  10. 理解 Memory barrier(内存屏障)【转】

    转自:http://name5566.com/4535.html 参考文献列表:http://en.wikipedia.org/wiki/Memory_barrierhttp://en.wikiped ...