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

题意

https://www.lydsy.com/JudgeOnline/problem.php?id=2595

思路

是一道比较裸的斯坦纳树呢~

题意等价于选出包含一些点的最小生成树,这就是斯坦纳树的功能。

举个例子,给定 \(n\) 个点,其中 \(k\) 个点被称作关键点,\(m\) 条带权边,求原图的一个权值最小的子图,这张子图图为包含这 \(k\) 个点的树。

我们定义 \(dp[i][j]\) 为关键点集合 \(i\) 与任意节点 \(j\) 连通的最小权的树。考虑转移这个 \(dp\) 数组,比较显然的是以下的子集划分:
\[
dp[i][j]=\min(dp[k][j]+dp[i\setminus k][j])
\]
其中 \(k\) 是 \(i\) 的子集。

当然这样转移是不够的,在关键点集合 \(i\) 不变的情况下,\(j\) 有可能会发生改变,即发生如下转移:
\[
\text{chk_min}(dp[i][k],dp[i][j]+w(j,k))
\]
其中 \(w(j,k)\) 为一条 \(j\) 指向 \(k\) 的边的边权。不难发现,这个过程和最短路的松弛操作是一样的,那么就可以利用最短路进行转移,没有负边就跑 \(\text{dijkstra}\),否则跑 \(\text{spfa}\) 。

这道题求的东西略微不同,是点有点权,不过无所谓,转移稍稍改动即可。然后还要输出方案,那么在 \(dp\) 转移的时候还需要记录从哪里转移过来。

代码

#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
template<typename T,typename _T>inline bool chk_min(T &x,const _T y){return y<x?x=y,1:0;}
template<typename T,typename _T>inline bool chk_max(T &x,const _T y){return x<y?x=y,1:0;}
typedef long long ll;
template<const int N,const int M,typename T>struct LinkedList
{
    int head[N],nxt[M],tot;T to[M];
    LinkedList(){clear();}
    T &operator [](const int x){return to[x];}
    void clear(){memset(head,-1,sizeof(head)),tot=0;}
    void add(int u,T v){to[tot]=v,nxt[tot]=head[u],head[u]=tot++;}
    #define EOR(i,G,u) for(int i=G.head[u];~i;i=G.nxt[i])
};
struct node
{
    int at,path;
    bool operator <(const node &_)const{return path>_.path;}
};

LinkedList<103,103*4,int>G;
std::priority_queue<node>Q;
int dp[(1<<10)+3][103];
bool lasknd[(1<<10)+3][103];
int las[(1<<10)+3][103];
bool mark[103];
int mp[103],ori[13];
int pw[103];
int n,m,K;

inline int hs(int x,int y){return x*m+y;}

void Steiner()
{
    FOR(i,0,(1<<K)-1)FOR(j,0,n-1)dp[i][j]=1e9;
    FOR(i,0,K-1)dp[1<<i][ori[i]]=0;
    FOR(i,1,(1<<K)-1)
    {
        FOR(j,0,n-1)
            for(int k=(i-1)&i;k;k=(k-1)&i)
                if(chk_min(dp[i][j],dp[k][j]+dp[i^k][j]-pw[j]))
                {
                    lasknd[i][j]=0;
                    las[i][j]=k;
                }
        while(!Q.empty())Q.pop();
        FOR(j,0,n-1)Q.push((node){j,dp[i][j]});
        while(!Q.empty())
        {
            node now=Q.top();Q.pop();
            int u=now.at;
            if(now.path>dp[i][u])continue;
            EOR(k,G,u)
            {
                int v=G[k],w=pw[v];
                if(chk_min(dp[i][v],dp[i][u]+w))
                {
                    lasknd[i][v]=1;
                    las[i][v]=u;
                    Q.push((node){v,dp[i][v]});
                }
            }
        }
    }
}

void backtrack(int i,int j)
{
    mark[j]=1;
    if(mp[j]!=-1&&i==(1<<mp[j]))return;
    if(!lasknd[i][j])
        backtrack(las[i][j],j),backtrack(i^las[i][j],j);
    else backtrack(i,las[i][j]);
}

int main()
{
    scanf("%d%d",&n,&m);
    FOR(i,0,n-1)FOR(j,0,m-1)
    {
        scanf("%d",&pw[hs(i,j)]);
        if(!pw[hs(i,j)])mp[hs(i,j)]=K,ori[K]=hs(i,j),K++;
        else mp[hs(i,j)]=-1;
    }
    FOR(i,0,n-1)FOR(j,0,m-2)
    {
        G.add(hs(i,j),hs(i,j+1));
        G.add(hs(i,j+1),hs(i,j));
    }
    FOR(i,0,n-2)FOR(j,0,m-1)
    {
        G.add(hs(i,j),hs(i+1,j));
        G.add(hs(i+1,j),hs(i,j));
    }
    n*=m;
    Steiner();
    int ans=1e9,id;
    FOR(i,0,n-1)if(chk_min(ans,dp[(1<<K)-1][i]))id=i;
    backtrack((1<<K)-1,id);
    printf("%d\n",ans);
    FOR(i,0,n-1)
    {
        if(!pw[i])putchar('x');
        else putchar(mark[i]?'o':'_');
        if(i%m==m-1)putchar('\n');
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Paulliant/p/11386625.html

时间: 2024-10-06 06:35:30

WC 2008 观光计划(斯坦纳树)的相关文章

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

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

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>

BZOJ2595: [Wc2008]游览计划(斯坦纳树,状压DP)

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

【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

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

题解:斯坦纳树,实现神马的在代码里面有还看得过去的注释. 代码: #include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define N 15 #define inf 0x3f3f3f3f using namespace std; const int dx[]={0,0,1,-1}; const int dy[

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 g

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

题意: 分析: 斯坦纳树裸题,有几个需要注意的地方 给出矩阵,不用自己建图,但枚举子集转移时会算两遍,需要减去当前点的权值 方案记录比较麻烦,两边的转移都需要记录,最后dfs找方案会比较容易理解 代码: #include <stdio.h> #include <string.h> #include <algorithm> #include <queue> using namespace std; #define N 110 #define LL long l

FJoi2017 1月20日模拟赛 直线斯坦纳树(暴力+最小生成树+骗分+人工构造+随机乱搞)

[题目描述] 给定二维平面上n个整点,求该图的一个直线斯坦纳树,使得树的边长度总和尽量小. 直线斯坦纳树:使所有给定的点连通的树,所有边必须平行于坐标轴,允许在给定点外增加额外的中间节点. 如下图所示为两种直线斯坦纳树的生成方案,蓝色点为给定的点,红色点为中间节点. [输入格式] 第一行一个整数n,表示点数. 以下n行,每行两个整数表示点的x,y坐标. [输出格式] 第一行一个整数m,表示中间节点的个数. 必须满足m <= 10 * n 以下m行,每行2个整数表示中间节点的坐标. 以下n+m-1