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

传送门

题意:略



论文 《SPFA算法的优化及应用》

http://www.cnblogs.com/lazycal/p/bzoj-2595.html

本题的核心就是求斯坦纳树:

Steiner Tree:

Given an undirected graph with non-negative edge weights and a subset of vertices, usually referred to as terminals,

the Steiner tree problem in graphs requires a tree of minimum weight that contains all terminals (but may include additional vertices).

也就是对于给定的点集求一颗包含他的最小生成树(可以包含额外的点)

$ST$是$NPC$问题,规模小的情况可以使用状压$DP$解决

$f[i][s]$表示根在$i$,连通的点集为$s$的(仅包括给定点集中的点)的最小花费

有两种转移:

对于点权的情况(边权类似):

$f[i][s]=min{f[i][s‘]+f[i][s-s‘]-val[i]}$,划分成两个子集,具有阶段性普通$DP$就可以

$f[i][s]=min{f[i‘][s]+val[i]}$,从一颗树扩展而来,阶段性不明显,但满足三角不等式,使用$spfa$求解

那么过程就很清楚了

  • 从小到大枚举集合和点
  • 第一种转移枚举子集
  • 第二种转移对当前集合使用spfa

然后就到黄学长哪里仿写了份模板

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
using namespace std;
#define pii pair<int,int>
#define MP make_pair
#define fir first
#define sec second
typedef long long ll;
const int N=12,S=(1<<10)+5,INF=1e9;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1;c=getchar();}
    while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘;c=getchar();}
    return x*f;
}

int n,m,k,a[N][N];
int f[N][N][S];
struct Path{
    int i,j,s;
    Path(){}
    Path(int a,int b,int c):i(a),j(b),s(c){}
}pre[N][N][S];

queue<pii> q;
bool inq[N][N];
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
void spfa(int s){
    while(!q.empty()){
        int x=q.front().fir,y=q.front().sec;
        inq[x][y]=0;q.pop();
        for(int k=0;k<4;k++){
            int i=x+dx[k],j=y+dy[k];
            if(i<1||i>n||j<1||j>m) continue;
            if(f[i][j][s]>f[x][y][s]+a[i][j]){
                f[i][j][s]=f[x][y][s]+a[i][j];
                pre[i][j][s]=Path(x,y,s);
                if(!inq[i][j])
                    q.push(MP(i,j)),inq[i][j]=1;
            }
        }
    }
}
bool vis[N][N];
void dfs(int x,int y,int s){
    vis[x][y]=1;
    Path t=pre[x][y][s];
    if(t.i==0&&t.j==0) return;
    dfs(t.i , t.j , t.s);
    if(t.i==x && t.j==y) dfs(t.i , t.j , s-t.s);
}
int main(){
    freopen("in","r",stdin);
    n=read();m=read();
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            a[i][j]=read();
            if(!a[i][j]) f[i][j][1<<k]=0,k++;
        }

    int All=1<<k;
    for(int sa=0;sa<All;sa++){
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                for(int s=sa&(sa-1);s;s=sa&(s-1)){
                    int _=f[i][j][s]+f[i][j][sa-s]-a[i][j];
                    if(_<f[i][j][sa]){
                        f[i][j][sa]=_;
                        pre[i][j][sa]=Path(i,j,s);
                    }
                }
                if(f[i][j][sa]<INF) q.push(MP(i,j)),inq[i][j]=1;
            }
        spfa(sa);
    }

    int x=0,y=0,flag=0;
    for(int i=1;i<=n&&!flag;i++)
        for(int j=1;j<=m;j++) if(!a[i][j]) {x=i;y=j;flag=1;break;}
    dfs(x,y,All-1);
    printf("%d\n",f[x][y][All-1]);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i][j]==0) putchar(‘x‘);
            else if(vis[i][j]) putchar(‘o‘);
            else putchar(‘_‘);
        }
        puts("");
    }
}
时间: 2024-08-05 04:40:48

BZOJ 2595: [Wc2008]游览计划 [DP 状压 斯坦纳树 spfa]【学习笔记】的相关文章

【BZOJ 2595】2595: [Wc2008]游览计划 (状压DP+spfa,斯坦纳树?)

2595: [Wc2008]游览计划 Time Limit: 10 Sec  Memory Limit: 256 MBSec  Special JudgeSubmit: 1572  Solved: 739 Description Input 第一行有两个整数,N和 M,描述方块的数目. 接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该方块为一个景点:否则表示控制该方块至少需要的志愿者数目. 相邻的整数用 (若干个) 空格隔开,行首行末也可能有多余的空格. Output 由 N

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

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

BZOJ 2595 Wc2008 游览计划 斯坦纳树

题目大意:给定一个矩阵,有一些关键点,每个格子有权值,选择一些格子使所有关键点连通,求最小权值和 传说中的斯坦纳树- - 感觉不是很难理解的样子 枚举连通的状态,对于每个状态先对每个位置枚举子集进行合并,然后对这个状态的分层图进行SPFA 看了几分代码还是ZKY写的比较简洁- - 此外就是终于能通过操作符重载访问结构体里的三维数组了- - 我真是太丧病了233 #include <cstdio> #include <cstring> #include <iostream>

BZOJ 1072: [SCOI2007]排列perm [DP 状压 排列组合]

题意:给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0) 100%的数据满足:s的长度不超过10, 1<=d<=1000, 1<=T<=15 看到整除应该往余数方面想 $f[s][i]$表示当前已经选择的数的集合为$s$,余数为$i$的方案数 枚举下一个数字,用更新的写法转移 注意是有重复元素的排列!除上个阶乘 #include <iostream> #include <cstdio> #include <cstring&g

[Wc2008]游览计划

2595: [Wc2008]游览计划 Time Limit: 10 Sec  Memory Limit: 256 MBSec  Special Judge[Submit][Status][Discuss] Description Input 第一行有两个整数,N和 M,描述方块的数目. 接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该方块为一个景点:否则表示控制该方块至少需要的志愿者数目. 相邻的整数用 (若干个) 空格隔开,行首行末也可能有多余的空格. Output 由 N

WC 2008 观光计划(斯坦纳树)

题意 https://www.lydsy.com/JudgeOnline/problem.php?id=2595 思路 是一道比较裸的斯坦纳树呢- 题意等价于选出包含一些点的最小生成树,这就是斯坦纳树的功能. 举个例子,给定 \(n\) 个点,其中 \(k\) 个点被称作关键点,\(m\) 条带权边,求原图的一个权值最小的子图,这张子图图为包含这 \(k\) 个点的树. 我们定义 \(dp[i][j]\) 为关键点集合 \(i\) 与任意节点 \(j\) 连通的最小权的树.考虑转移这个 \(dp

HDU 4352 XHXJ&#39;s LIS 数位DP + 状压

由LIS的nlogn解法 可以得出最后统计数组中数的个数即为LIS的长度 这样就可以状压了 #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <climits> #include <string> #include <iostream> #include <map> #include <c

2017.8.15 [Haoi2016]字符合并 区间dp+状压dp

[题目描述] 有一个长度为n的01串,你可以每次将相邻的k个字符合并,得到一个新的字符并获得一定分数.得到的新字符和分数由这k个字符确定.你需要求出你能获得的最大分数. [输入格式] 第一行两个整数n,k. 接下来一行长度为n的01串,表示初始串.输入的的相邻字符之间用一个空格隔开. 接下来2k行,每行一个字符ci和一个整数wi,ci表示长度为k的01串连成二进制后按从小到大顺序得到的第i种合并方案得到的新字符, wi表示对应的第i种方案对应获得的分数. [输出格式] 输出一个整数表示答案. [

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

[BZOJ2595][Wc2008]游览计划 Description Input 第一行有两个整数,N和 M,描述方块的数目. 接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该方块为一个景点:否则表示控制该方块至少需要的志愿者数目. 相邻的整数用 (若干个) 空格隔开,行首行末也可能有多余的空格. Output 由 N + 1行组成.第一行为一个整数,表示你所给出的方案中安排的志愿者总数目. 接下来 N行,每行M 个字符,描述方案中相应方块的情况: z  ‘_’(下划线)表示该