bzoj4774 修路(斯坦纳树)

题目链接

分析:
第一次看到这道题,觉得就是一个MST
但是我明明找到的斯坦纳树的练习啊。。。

实际上不是很明白MST和斯坦纳树的区别,好像都是最小网络
不过斯坦纳树可以在给出点外另找点,并且不要求联通所有点
大概就是能处理有后效性的图上状压dp ???

浅谈斯坦纳树
时隔多年多月,要不是今天Mario_sz给我讲了一下,我可能永远都蒙在鼓里
这次终于通过这道题有一点点理解了

总的来说
首先我们设计状态: f[i][j] f [ i ] [ j ] 表示根为 i i ,连通状态为j的最小代价(状态只李记录关键点)

有两种转移方法:

  • 枚举子树的形态: f[i][j]=min(f[i][j]f[i][k]+f[i][l]) f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ k ] + f [ i ] [ l ] ) ,其中 k k l是对 j j 的一个划分
  • 按照边进行松弛:f[i][j]=min(f[i][j]f[i][j]+w[i][i]),其中 i i i之间有边相连

对于第一种转移,我们直接枚举子集
对于第二种转移,我们用spfa进行状态转移

枚举状态集S 
{ 
     枚举S的子集s 
     { 
         更新f[1~n][S]
     } 
     将 f[x][S]<inf 的x入队 
     spfa(S) 
}

这道题就会说构造包含 2d 2 d 个点的斯坦纳树

tip

斯坦纳树的状态只记录关键点

为什么狂WA不止?
因为初始化的时候要用 0x33 0 x 33 ,不要乱用(反正不要用0x7f就能A了)

看到网上前辈的代码,在最后都会再转移一个 g g
g(s)表示连通的状态为 s s 的最小代价,枚举前d个点的连通状况 S S
为了满足题意,强制i<>ni+1已经连通: S Xor (S<<d) S   X o r   ( S << d )
多了这个转移就是保证 i<>ni+1 i < − > n − i + 1 一定连通的情况下
再转移 1d 1 − d 连通的最小代价(没有会WA哦)

for (int S=1;S<(1<<d);S++)
    for (int i=1;i<=n;i++)
        g[S]=min(g[S],f[S^(S<<d)][i]); 
for (int S=1;S<(1<<d);S++)
    for (int s=(S-1)&S;s;s=(s-1)&S)
        g[S]=min(g[S],g[s]+g[S^s]);

为什么狂T不止?

一个很奇怪的现象:

我一开始设计的状态: f[n][S] f [ n ] [ S ] ,前一维表示点,后一维表示状态, f[10000][260] f [ 10000 ] [ 260 ]
怪异的T了
把两维的顺序换了一下: f[S][n] f [ S ] [ n ] ,前一维表示状态,后一维表示点, f[260][10000] f [ 260 ] [ 10000 ]
表面上没有任何区别,但是这样才能A掉
瑟瑟发抖,不知道什么原因,问了一下曲神
这里写图片描述

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>

using namespace std;

const int INF=0x33333333;
const int N=10005;
int f[260][N],g[260],st[N],tot=0,n,m,d;
struct node{
    int y,v,nxt;
};
node way[N<<1];
bool in[N];
queue<int> q;

void add(int u,int w,int z) {
    tot++;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
    tot++;way[tot].y=u;way[tot].v=z;way[tot].nxt=st[w];st[w]=tot;
}

void spfa(int S) {
    while (!q.empty()) {
        int now=q.front(); q.pop();
        in[now]=0;
        for (int i=st[now];i;i=way[i].nxt) {
            int y=way[i].y;
            if (f[S][y]>f[S][now]+way[i].v) {
                f[S][y]=f[S][now]+way[i].v;
                if (!in[y]) {
                    in[y]=1;
                    q.push(y);
                }
            }
        }
    }
}

void solve() {
    memset(f,0x33,sizeof(f));
    int cnt=2*d;

    for (int i=1;i<=d;i++) f[1<<i-1][i]=0,f[1<<d+i-1][n-i+1]=0;
    for (int S=1;S<(1<<cnt);S++) {
        for (int s=(S-1)&S;s;s=(s-1)&S) {
            int t=S^s;
            for (int i=1;i<=n;i++)
                f[S][i]=min(f[S][i],f[s][i]+f[t][i]);
        }
        for (int i=1;i<=n;i++)
            if (f[S][i]<INF&&!in[i])
                q.push(i),in[i]=1; 
        spfa(S);
    } 

    memset(g,0x33,sizeof(g));
    for (int S=1;S<(1<<d);S++)
        for (int i=1;i<=n;i++)
            g[S]=min(g[S],f[S^(S<<d)][i]);
    for (int S=1;S<(1<<d);S++)
        for (int s=(S-1)&S;s;s=(s-1)&S)
            g[S]=min(g[S],g[s]+g[S^s]);
    printf("%d",g[(1<<d)-1]==INF? -1:g[(1<<d)-1]);
}

int main()
{
    scanf("%d%d%d",&n,&m,&d);
    for (int i=1;i<=m;i++) {
        int u,w,z;
        scanf("%d%d%d",&u,&w,&z);
        add(u,w,z);
    }
    solve();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值