UVa 1496 - Peach Blossom Spring

提示: 

1. 看到题目 , 最短路? 最小费用流?(博主才做完图论回来做这道题的第一反应) ... 可能你会想到枚举对应关系然后写k个最短路 ,但是可能存在一些点彼此联通 , 共用一些边 导致独立求最短路的情形是错误的。 我们不妨先将问题弱化一下 , 如果求这些点(前K个 和 后K个)彼此都联通, 你想到了什么?

2.  看完提示1 没有思路说明你和当时博主一样不熟悉一个根据动态规划为原理 , 广泛应用于组合的囊括最小生成树的一种树 , 斯坦纳树

3.  斯坦纳树算法就能求使一个给定点集内点彼此联通的最小代价(所包含的边可以是连接这个点集以外的点) , 如果用提示1.中的弱化问题 , 这就是一个裸的斯坦纳树题目(其方法会在之后介绍)

4.  假设你能够求出给定点集的斯坦纳树 , 你还是无法想出此题的思路往下看。 本题并不要求所有点联通 , 但至少两两联通 , 那么可以把答案看成几颗斯坦纳树 , 你的任务就是枚举这些树的可能形态 , 后来你会发现 , 那也是个动态规划   (dp是思想 , 不是具体方法)

5. 斯坦纳树网上有很多文章 , 可以到网上搜详细讲解 , 这里只给出dp状态转移方程和评价:


d [i] [s]  : 以i 为树根 , 包含集合s中所有点(s用二进制表示 , 并且s仅是所选点集中的点) , 不难发现状态数为 n*(2^k)  // n为节点数 , k 为所选点的个数


这里强调一下dp中一个重要概念 , 状态的顺序 , 如果两个状态a , b , a的dp值可以影响b的dp值 ,但是b的dp值不能影响a的dp值 , 简单点说就是只能用a推出b , 那么我们就说a的状态比b的状态靠前 , 但如果a,b可以互相影响 , 那么我们就说这两个状态是同层的   (同层dp , 见过吗 , 刘汝佳紫书上有一题)


斯坦纳树就是一个需要考虑同层dp的问题 , 于是 dp 的状态转移方程分成两个部分:

从前往后推:  d [ i ] [ s ] = min( d [ i ] [s'] + d [ i ] [ s-s' ] )  // 其中s'为s的子集  , 所以说是从前往后推

同层dp : d [ i ] [ s ] = d [ j ] [ s ] + dis [ i ] [ j ]  // 其中dis[i][j]为 i 到 j 的最短路 , 这个可以用floyd预处理 , 当然也可以现场写dijkstra 或者 bellmanFord(SPFA)


当然答案就是min( d [ i ] [ 所选点集的全集 ] ) 


后面提到的计算答案的动态规划看代码 , 注意斯坦纳树只需要求一次就可以了


博主的疑问:  同层dp一般来说是需要写dijkstra等最短路算法的 , 但是此题直接更新也没有问题 ,博主这里有些疑惑, 如果读者能解答 , 望告知博主(QQ:812483101) ,

Update: 由于博主没有写主流的SteinerTree的版本, 其实所谓的SPFA就是同层转移 , 可以网上BZOJ4006的代码和讲解来看看主流的SteinerTree的写法。

注意: 用addEdge初始化数组 f 的时候一定要加min , 博主被坑过


//
//  main.cpp
//  UVa1496
//
//  Created by Fuxey on 15/10/19.
//  Copyright © 2015年 corn.crimsonresearch. All rights reserved.
//

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <deque>
#include <string>
#include <stack>
#include <algorithm>

using namespace std;
const int maxn = 60;
const int maxk = 7*2;
const int INF = 1<<29;

struct edge
{
    int t , v;
    edge(int t=0 ,int v=0):t(t),v(v){}
};

struct StreinerTree
{
    int n;
    int f[maxn][maxn] , d[maxn][1<<maxk];
    int book[maxn];
    void init(int n)
    {
        this->n = n;
        for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) f[i][j] = INF;
    }
    
    void addEdge(int ff , int t , int v)
    {
        f[ff][t] = min(v , f[ff][t]);
        f[t][ff] = min(v , f[t][ff]);
    }
    
    void floyd()
    {
        for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
            f[i][j] = min(f[i][j] , f[i][k]+f[k][j]);
    }
    
    void mincostST(vector<int> s)
    {
        for(int i=1;i<=n;i++) for(int j=0;j<(1<<s.size());j++) d[i][j] = INF;
        
        for(int i=0;i<s.size();i++) d[s[i]][1<<i] = 0;
        for(int i=1;i<(1<<s.size());i++)
        {
            for(int j=1;j<=n;j++) for(int ii = (i-1)&i;ii>0;ii = (ii-1)&i)
                d[j][i] = min(d[j][i] , d[j][ii]+d[j][i-ii]);
            
            for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) d[j][i] = min(d[j][i] , d[k][i]+f[j][k]);
           /* memset(book, 0, sizeof(book));
            for(int j=1;j<=n;j++)
            {
                int Min = INF , x = 0;
                for(int k=1;k<=n;k++) if(Min >= d[k][i] && book[k]==0) Min = d[k][i] , x = k;
                book[x] = 1;
                for(int k=1;k<=n;k++) d[k][i] = min(d[k][i] , d[x][i]+f[x][k]);
            } */  // 这里就是那个n^2的dijkstra的更新方法 , 博主认为这样会更可靠一些
        }
    }
}solver;

int ans[1<<(maxk/2)][1<<(maxk/2)];

int main(int argc, const char * argv[]) {
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--)
    {
        int n , m , K;
        cin>>n>>m>>K;
        solver.init(n);
        while(m--)
        {
            int a , b , c;
            cin>>a>>b>>c;
            solver.addEdge(a, b, c);
        }
        solver.floyd();
        
        vector<int> s;
        for(int i=1;i<=K;i++) s.push_back(i); for(int i=n-K+1;i<=n;i++) s.push_back(i);
        solver.mincostST(s);
        
        for(int s1 = 1;s1<(1<<K);s1++) for(int s2 = 1;s2<(1<<K);s2++)
            if(__builtin_popcount(s1) == __builtin_popcount(s2))
            {
                int now = s1+(1<<K)*s2;
                ans[s1][s2] = INF;
                for(int i=1;i<=n;i++) ans[s1][s2] = min(ans[s1][s2] , solver.d[i][now]);
                for(int i=(s1-1)&s1;i>0;i = (i-1)&s1) for(int j=(s2-1)&s2;j>0;j = (j-1)&s2)
                    if(__builtin_popcount(i) == __builtin_popcount(j))
                    ans[s1][s2] = min(ans[s1][s2] , ans[i][j]+ans[s1-i][s2-j]);
            }
        if(ans[(1<<K)-1][(1<<K)-1]==INF) cout<<"No solution\n";
        else cout<<ans[(1<<K)-1][(1<<K)-1]<<endl;
    }
    
    return 0;
}


你可能感兴趣的:(dp,图论,uva,斯坦纳树)