BZOJ 2595 wc2008 浏览计划 [斯坦纳树]

本文深入探讨了斯坦纳树问题,一种经典的NP完全问题。详细介绍了如何通过状态压缩DP算法解决点权斯坦纳树问题,包括算法原理、实现步骤及代码示例。适合对图论和算法设计感兴趣的读者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

斯坦纳树裸题

但是本题的斯坦纳树是点权,与大多数图的边权不同,下边讲解一般的边权做法,然后再将本题的题解。

 

斯坦纳树:给出一些点,选出若干条边使得这些点连通,求总边权的最值。

斯坦纳树是NP问题,不存在多项式时间内的解法,求解方法是状压dp。

设 f[i][j]表示选择若干条边,使得状态为 ii 的给定点连通,并且当前可以选择下一条边的端点为 jj 的最小边权和。初始状态 f[2k][pos[k]]=0,其中 pos[k] 为第 kk 个给定点的编号。

那么我们对于每个 i 和 j ,首先枚举 i的子集 k ,用 f[k][j]+f[i−k][j] 更新 f[i][j]f[i][j] 。

然后再考虑同层转移:如果 x 与 y 边权为 z ,用f[i][x]+z 更新f[i][y] ,用f[i][y] 更新 f[i][x] 。容易发现这个转移就是最短路,因此使用堆优化Dijkstra跑一遍得出所有的 f[i][j] 。

最终答案就是 min{f[2p−1][i]}

这个dp的理解比较显然,时间复杂度O(3p·n+2p·mlog⁡n) ,其中 p 是给定点的数目。

 

那么对于本题,由于权值在点上,因此枚举子集转移时,当前点会重复选择,需要减去代价。

在同层转移时,求的就是点权最短路,边长为目标点的点权。

设 f[i][j][k]表示选定若干条边,使得状态为 ii 的给定点连通,并且当前可以选择下一条边的相邻点为 (j,k)(j,k) 的最小点权和。初始状态 f[2k][posx[k]][posy[k]]=0 .

首先枚举子集转移:f[i][j][k]=minl⊆if[l][j][k]+f[i−l][j][k]−a[j][k] ,然后同层转移,与 (j,k)相邻的点的 f 加上 a[j][k] 可以转移到f[i][j][k] 。

输出方案的话直接记录路径,记录从哪种方式的哪个状态转移过来,dfs一遍即可知道选定的点。

时间复杂度 O(3p⋅nm+2p⋅nmlogn)

/*
    斯坦纳树
*/

#include <bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
#define PII pair<int,int>

using namespace std;
const int maxn = 1050;
const int INF  = 1e9;
int N, M, tot = 0 ;
int a[12][12], f[12][12][maxn];
int dir[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
int vis[12][12];
struct PRE{
    int x, y, S;
}pre[12][12][maxn];
queue<PII> q;

void SPFA(int cur){
    while (q.size() != 0){
        PII p = q.front();q.pop();
        vis[p.fi][p.se] = 0;
        for (int i = 0 ; i < 4; i++){
            int nxtx = p.fi + dir[i][0], nxty = p.se + dir[i][1];
            if (nxtx < 1 || nxtx > N || nxty < 1 || nxty > M) continue;
            if (f[nxtx][nxty][cur] > f[p.fi][p.se][cur] + a[nxtx][nxty]){
                f[nxtx][nxty][cur] = f[p.fi][p.se][cur] + a[nxtx][nxty];
                pre[nxtx][nxty][cur] = (PRE){p.fi, p.se, cur};
                if (!vis[nxtx][nxty]){
                    vis[nxtx][nxty] = 1, q.push(mp(nxtx, nxty));
                }
            }
        }
    }
}

void dfs(int x, int y, int now){
    vis[x][y] = 1;
    PRE tmp = pre[x][y][now];
    if (tmp.x == 0 && tmp.y == 0) return ;
    dfs(tmp.x, tmp.y, tmp.S);
    if (tmp.x == x && tmp.y == y) dfs(tmp.x, tmp.y, now - tmp.S);
}

int main(){
    scanf("%d%d", &N, &M);
    memset(f, 0x3f, sizeof(f));
    for (int i = 1; i <= N; i++)
        for (int j = 1; j <= M; j++){
            scanf("%d", &a[i][j]);
            if (a[i][j] == 0) f[i][j][1<<tot] = 0, tot++;
        }
    int limit = (1 << tot) - 1;
    for (int sta = 0; sta <= limit; sta++){
        for (int i = 1; i <= N; i++)
            for (int j = 1; j <= M; j++){
                for (int s = sta & (sta - 1); s ; s = (s - 1) & sta){
                    if (f[i][j][s] + f[i][j][sta - s] - a[i][j] < f[i][j][sta]){
                        f[i][j][sta] = f[i][j][s] + f[i][j][sta - s] - a[i][j];
                        pre[i][j][sta] = (PRE){i,j,s};
                    }
                }
                if (f[i][j][sta] < INF) q.push(mp(i,j)), vis[i][j] = 1;
            }
        SPFA(sta);
    }
    int ansx, ansy, flag = 0;
    for (int i = 1; i <= N && !flag; i++)
            for (int j = 1; j <= M; j++)
                if (!a[i][j]){
                    ansx = i;
                    ansy = j;
                    flag = 1;
                    break;
                }
    printf("%d\n", f[ansx][ansy][limit]);
    memset(vis, 0, sizeof(vis));
    dfs(ansx, ansy, limit);
    for (int i = 1; i <= N; i++, puts("")) {
        for (int j = 1; j <= M; j++){
            if (a[i][j] == 0) putchar('x');
            else if (vis[i][j]) putchar('o');
            else putchar('_');
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值