UVALive 5717 Peach Blossom Spring 斯坦纳树

题目大意:给N个点,M条边,边有长度,构成一个图。前K个点住的有居民,最后K个点是避难所。

要求你从这M条边中选出一些边,使得每个居民都能到达一个避难所而且选的边的总长度最少。


参考资料:《SPFA算法的优化及应用

http://endlesscount.blog.163.com/blog/static/821197872012525113427573/


从题意能推断出,最终的解是一棵棵树组成的森林。


我们先对每棵树的模型进行求解,因为知道每个状态情况对应的树的最优解,最后压缩DP一下就能求森林的最优解了。


而对于每棵树,最直接的模型就是《SPFA算法的优化及应用》里游览计划那道题。

SPFA的思想就是用当前的最优解来松弛更新和当前相关的状态的最优解。

近似于DP,不过在有的时候避免了一些不必要的更新。


这题就是如此,DP[ i ][ j ] 表示以i根的时候,状态为j的当前的最优解,它能更新的状态有两个


枚举子树的形态:dp[ i ][ j ]=min{ dp[ i ][ j ],dp[ i ][ k ]+dp[ i ][ l ] },其中k和l是对j的一个划分。
按照边进行松弛:dp[ i ][ j ]=min{ dp[ i ][ j ],dp[ i' ][ j ]+w[ i ][ i' ] },其中i和i'之间有边相连。

第一个形态直接更新到一个状态,用for循环一下就好。
第二个更新用spfa。

理论就这么简单,具体实现上有许多技巧就是。


#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <stack>
#include <set>
#include <map>
using namespace std;
#define REP(i,n)   for(int i=0;i<(n);++i)
#define FOR(i,l,r) for(int i=(l);i<=(r);++i)
#define DSC(i,r,l) for(int i=(r);i>=(l);--i)
#define N 52
#define NN (1<<10)
#define M 2010
#define INF 1e9
int pos[N];
int dp[N][NN];
bool visit[N][NN];
int ans[NN];

int n,nn,m,K;
int head[N],ip;
struct Edge
{
    int to,next,c;
}edge[M];
void add(int u,int v,int c)
{
    edge[ip].to=v;edge[ip].c=c;edge[ip].next=head[u];head[u]=ip++;
}
struct Node
{
    int x,y;
    Node(){}
    Node(int x,int y):x(x),y(y){}
};
queue<Node>q;

void init()
{
    FOR(i,1,n)
        REP(j,nn)
            dp[i][j]=INF;
    memset(pos,0,sizeof(pos));
    FOR(i,1,K)
    {
        pos[i]=1<<(i-1);
        dp[i][ pos[i] ]=0;
        pos[n-i+1]=1<<(K+i-1);
        dp[n-i+1][ pos[n-i+1] ]=0;
        //状态压缩时只压缩前K个点和最后K个点就好
        //最后在求解时有一个check()函数,保证了每棵树都是合法的
    }
}

void spfa()//仅对第二个松弛进行spfa
{
    while(!q.empty())
    {
        int x=q.front().x,y=q.front().y;
        q.pop();
        visit[x][y]=0;

        for(int p=head[x];p!=-1;p=edge[p].next)
        {
            int xx=edge[p].to,yy=y|pos[xx];
            int temp=dp[x][y]+edge[p].c;

            if(temp<dp[xx][yy])
            {
                dp[xx][yy]=temp;
                if(y==yy && !visit[xx][yy])
                {
                    visit[xx][yy]=1;
                    q.push(Node(xx,yy));
                }
            }
        }
    }
}
bool check(int y)//保证当前状态中,存在的前K个点的数目和最后K个点存在的数量相同
{                //由此当前状态中每个居民必定和一个避难所相连
    int num1=0,num2=0;
    REP(i,K<<1)
    {
        if(y&(1<<i))
        {
            if(i<K) num1++;
            else num2++;
        }
    }
    return num1==num2;
}

void solve()
{
    REP(y,nn)//把第一个松弛完全剥离出来了,按每个状态进行更新
    {
        FOR(x,1,n)
        {
            for(int i=y&(y-1);i;i=y&(i-1))//这句话枚举出了当前y状态包含的所有状态,自己在纸上模拟一下
                dp[x][y]=min(dp[x][y],dp[x][ i|pos[x] ] + dp[x][ (y-i)|pos[x] ]);
            if(dp[x][y]<INF) q.push(Node(x,y));
        }
        spfa();
    }
    REP(y,nn)
    {
        ans[y]=INF;
        FOR(i,1,n) ans[y]=min(ans[y],dp[i][y]);
    }
    //此时状态已经求解完了,最后DP出答案就好
    REP(y,nn)
    {
        if(check(y))
        {
            for(int i=(y-1)&y;i;i=(i-1)&y)
            {
                if(check(i))
                {
                    ans[y]=min(ans[y],ans[i]+ans[y-i]);
                }
            }
        }
    }
}

int main()
{
    //freopen("in","r",stdin);
    int cas;
    cin>>cas;
    int x,y,c;
    while(cas--)
    {
        memset(head,-1,sizeof(head)); ip=0;
        cin>>n>>m>>K;
        nn=(1<<(K<<1));

        init();
        while(m--)
        {
            scanf("%d%d%d",&x,&y,&c);
            add(x,y,c);
            add(y,x,c);
        }
        solve();

        if(ans[nn-1]>=INF) puts("No solution");
        else cout<<ans[nn-1]<<endl;
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值