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

Posted paulliant

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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\) 有可能会发生改变,即发生如下转移:
\[ \textchk_min(dp[i][k],dp[i][j]+w(j,k)) \]
其中 \(w(j,k)\) 为一条 \(j\) 指向 \(k\) 的边的边权。不难发现,这个过程和最短路的松弛操作是一样的,那么就可以利用最短路进行转移,没有负边就跑 \(\textdijkstra\),否则跑 \(\textspfa\)

这道题求的东西略微不同,是点有点权,不过无所谓,转移稍稍改动即可。然后还要输出方案,那么在 \(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 &_)constreturn 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;

以上是关于WC 2008 观光计划(斯坦纳树)的主要内容,如果未能解决你的问题,请参考以下文章

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

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

[WC2008]游览计划 「斯坦那树模板」

bzoj2595: [Wc2008]游览计划 斯坦纳树

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

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