BZOJ 4774 修路

斯坦纳树DP

斯坦纳树经典DP方程: f[s][i] 表示当前需连通点的连通情况至少为 s ,且路径上一定经过点i的代价。

为什么说是至少为 s ,因为可能方案里的路径经过了别的点,然而在当前状态下我们只考察这s个点,所以可以忽视别的点。

经典的两步转移:

f[s][i]=f[s][i]+f[ss][i]()

f[s][i]=f[s][j]+w[j][i](ji)

一个结论: f[s][i] 对应的方案的路径一定可以表示成一棵树的形态。

证明:显然如果路径上有一个环,我们总能拆掉环上一条边而代价不会更劣。

我们考虑一个状态 f[s][i] ,分三种情况来证明上述转移的正确性。

我们已经知道它最优方案是一棵树,假设这棵树在根的部位分叉了,则显然可以从分叉的(两个)不同的子集转移过来,即第一个转移就能满足要求。

否则这棵树在根的地方不分叉,而在底下分叉了,则我们可以通过在底下分叉的那个点的DP值,通过第二个转移得到答案。

还有一种情况是这棵树就是一条链,或者只是一个点。要么这是初始化的时候,这样显然正确。否则就是所有连通点在一条链上,那我们就能根据从 i <script type="math/tex" id="MathJax-Element-62">i</script>往下第一个连通点的DP值,同样通过第二个转移得到答案。

没有别的情况了,所以这个转移正确。

然后这题随便搞搞就行了。

 #include<cstdio>
#include<cstring>
#include<algorithm>
#define N 10005
using namespace std;
namespace runzhe2000
{
    const int S = N-1, INF = 1<<29;
    int n, m, d, ecnt, last[N], f[1<<8][N], dis[N], vis[N], g[1<<4];
    struct edge{int next, to, val;}e[N<<2];
    void addedge(int a, int b, int c)
    {
        e[++ecnt] = (edge){last[a], b, c};
        last[a] = ecnt;
    }

    struct heap
    {
        int x, d;
        bool operator < (const heap &that) const {return d < that.d;}
    }h[N<<1]; int tot;
    void heap_insert(int x, int d) 
    {
        h[++tot] = (heap){x, d};
        for(int i = tot, j = i >> 1; j; i = j, j >>= 1)
        {
            if(h[i] < h[j]) swap(h[i], h[j]);
            else break;
        }
    }
    void heap_pop()
    {
        h[1] = h[tot--];
        for(int i = 1, j = i << 1; j <= tot; i = j, j <<= 1)
        {
            if(h[j|1] < h[j]) j |= 1;
            if(h[j] < h[i]) swap(h[i], h[j]);
            else break;
        }
    }

    void dijk()
    {
        memset(dis, 63, sizeof(dis)); memset(vis, 0, sizeof(vis));
        dis[S] = 0; tot = 0; heap_insert(S, dis[S] = 0);
        for(; tot; )
        {
            int x = h[1].x; heap_pop();
            if(vis[x]) continue; vis[x] = 1;
            for(int i = last[x]; i; i = e[i].next)
            {
                int y = e[i].to;
                if(dis[x] + e[i].val < dis[y])
                    heap_insert(y, dis[y] = dis[x] + e[i].val);
            }
        }
    }

    void main()
    {
        scanf("%d%d%d",&n,&m,&d);
        for(int i = 1, x, y, z; i <= m; i++)
        {
            scanf("%d%d%d",&x,&y,&z);
            addedge(x, y, z);
            addedge(y, x, z);
        }
        int tmpecnt = ecnt; memset(f, 63, sizeof(f));
        for(int i = 1; i <= n; i++) if(i <= d || n-d+1 <= i)
        {
            ecnt = tmpecnt; last[S] = 0; addedge(S, i, 0); dijk();
            int s = (i <= d) ? 1<<(i-1) : 1<<(d+n-i);
            for(int j = 1; j <= n; j++) 
                f[s][j] = dis[j];
        }
        memset(g, 63, sizeof(g));
        for(int sta = 2, _sta = 1<<(d*2); sta < _sta; sta++)
        {
            ecnt = tmpecnt; last[S] = 0;
            for(int i = 1; i <= n; i++)
            {
                for(int s = (sta-1)&sta; s; s = (s-1)&sta)
                    f[sta][i] = min(f[sta][i], f[s][i] + f[sta-s][i]);
                addedge(S, i, f[sta][i]);
            }
            dijk();
            int s = sta & (sta>>d);
            for(int i = 1; i <= n; i++)
            {
                f[sta][i] = dis[i];
                g[s] = min(g[s], dis[i]);
            }
        }
        for(int sta = 2, _sta = 1<<d; sta < _sta; sta++)
            for(int s = (sta-1)&sta; s; s = (s-1)&sta)
                g[sta] = min(g[sta], g[s] + g[sta - s]);
        printf("%d\n",g[(1<<d)-1] < INF ? g[(1<<d)-1] : -1);
    }
}
int main()
{
    runzhe2000::main();
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值