【HDU3311】Dig The Wells-状压DP+SPFA:斯坦纳树

测试地址:Dig The Wells
题目大意:n个寺庙和m个其他地点,每个寺庙或者地点都可以挖井,现在要修建一些道路,使得每一个寺庙都至少与一个井连通,挖井和修路都需要费用,求最小费用。
做法:本题需要用到状压DP+SPFA。
什么是斯坦纳树?在一个图中,连通一个关键点集并使得所选边权和最小的边集构成一棵树,这就是斯坦纳树。
斯坦纳树可以这样求:令dp(i,state)为选择i点为树根,和关键点连通的状态为state的最小费用,状态转移方程有两个部分:
第一个部分是f(i,state)=min(f(i,subset)+f(i,statesubset)),其中subsetstate的一个子集。这一个部分是用以自己为根的连通状态组合来更新的答案。这一部分显然可以用枚举子集的状压DP做到O(m3n)
第二个部分是f(i,state)=min(f(j,state)+dis(i,j)),其中dis(i,j)为点i和点j的距离。这一个部分是用以其他点为根的相同连通状态来更新答案。我们发现这个方程很像最短路算法中的“松弛”操作,因此我们用SPFA来进行松弛,那么这一个部分的时间复杂度为O(kp2n),其中k是SPFA算法的常数。
那么这道题基本上就是这样做的,然而还是有不同,这道题可以有很多不同的连通块,所以我们可以先按照上面的方法DP,然后求出fmin(state),表示连通state中的这些点所需要的最小费用,这里的费用包括打井的费用,接着令d(i,state)为选了i个连通块,已选点的集合为state的最小费用,则有:
d(i,state)=min(d(i1,subset)+fmin(statesubset))
这个显然可以用枚举子集的状压DPO(n3n)算出,那么最后max(d(i,2n1))就是所求的答案。
我傻逼的地方:在用1表示无穷大的时候,一定要记得确认将要进行的运算中没有包含这种无穷大的数字……WA了好几发……
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,p;
int first[1010],tot,st[50],num[50];
ll val[1010],dp[1010][50],d[2][50],mn[50];
bool vis[1010]={0};
queue<int> Q;
struct edge
{
    int v,next;
    ll w;
}e[10010];

void insert(int a,int b,ll w)
{
    e[++tot].v=b;
    e[tot].next=first[a];
    e[tot].w=w;
    first[a]=tot;
}

bool cmp(int a,int b)
{
    return num[a]<num[b];
}

void init()
{
    for(int i=1;i<=n+m;i++)
        scanf("%lld",&val[i]);

    memset(first,0,sizeof(first));
    tot=0;
    for(int i=1;i<=p;i++)
    {
        int a,b;
        ll w;
        scanf("%d%d%lld",&a,&b,&w);
        insert(a,b,w),insert(b,a,w);
    }

    for(int i=0;i<(1<<n);i++)
        for(int j=1;j<=n+m;j++)
            dp[j][i]=-1;
    for(int i=n+1;i<=n+m;i++)
        dp[i][0]=0;

    for(int i=0;i<(1<<n);i++)
        st[i]=i;
    sort(st,st+(1<<n),cmp);
}

void spfa(int state)
{
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (dp[e[i].v][state]==-1||dp[e[i].v][state]>dp[v][state]+e[i].w)
            {
                dp[e[i].v][state]=dp[v][state]+e[i].w;
                if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v);
            }
        vis[v]=0;
    }
}

void work()
{
    for(int i=1;i<(1<<n);i++)
    {
        int x=st[i];
        for(int j=1;j<=n+m;j++)
        {
            dp[j][x]=-1;
            if (j<=n&&x==(1<<(j-1))) dp[j][x]=0;
            for(int k=x;k;k=(k-1)&x)
                if (dp[j][k]!=-1&&dp[j][x-k]!=-1)
                {
                    if (dp[j][x]==-1) dp[j][x]=dp[j][k]+dp[j][x-k];
                    dp[j][x]=min(dp[j][x],dp[j][k]+dp[j][x-k]);
                }
            if (dp[j][x]!=-1) Q.push(j),vis[j]=1;
        }
        spfa(x);
        mn[x]=-1;
        for(int j=1;j<=n+m;j++)
            if (dp[j][x]!=-1)
            {
                if (mn[x]==-1) mn[x]=dp[j][x]+val[j];
                mn[x]=min(mn[x],dp[j][x]+val[j]);
            }
    }

    int now=1,past=0;
    for(int i=0;i<(1<<n);i++)
        d[past][i]=-1;
    d[past][0]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<(1<<n);j++)
        {
            d[now][j]=d[past][j];
            for(int k=j;k;k=(k-1)&j)
                if (d[past][j-k]!=-1&&mn[k]!=-1)
                {
                    if (d[now][j]==-1) d[now][j]=d[past][j-k]+mn[k];
                    d[now][j]=min(d[now][j],d[past][j-k]+mn[k]);
                }
        }
        swap(now,past);
    }
    printf("%lld\n",d[past][(1<<n)-1]);
}

int main()
{
    for(int i=0;i<=45;i++)
    {
        num[i]=0;
        int x=i;
        while(x)
        {
            num[i]++;
            x-=(x&(-x));
        }
    }

    while(scanf("%d%d%d",&n,&m,&p)!=EOF)
    {
        init();
        work();
    }

    return 0;
}
posted @ 2018-05-07 11:17  Maxwei_wzj  阅读(117)  评论(0编辑  收藏  举报