HDU3311Dig The Wells(斯坦纳树,spfa+状态压缩DP)可作模板

Dig The Wells

Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 1037    Accepted Submission(s): 453


Problem Description
You may all know the famous story “Three monks”. Recently they find some places around their temples can been used to dig some wells. It will help them save a lot of time. But to dig the well or build the road to transport the water will cost money. They do not want to cost too much money. Now they want you to find a cheapest plan.
 

Input
There are several test cases.
Each test case will starts with three numbers n , m, and p in one line, n stands for the number of monks and m stands for the number of places that can been used, p stands for the number of roads between these places. The places the monks stay is signed from 1 to n then the other m places are signed as n + 1 to n + m. (1 <= n <= 5, 0 <= m <= 1000, 0 <=p <= 5000)
Then n + m numbers followed which stands for the value of digging a well in the ith place.
Then p lines followed. Each line will contains three numbers a, b, and c. means build a road between a and b will cost c.
 

Output
For each case, output the minimum result you can get in one line.
 

Sample Input
  
  
3 1 3 1 2 3 4 1 4 2 2 4 2 3 4 4 4 1 4 5 5 5 5 1 1 5 1 2 5 1 3 5 1 4 5 1
 

Sample Output
  
  
6 5
 

Author
dandelion
 

Source

【题目大意】

    给定N个寺庙,和M个另外的地方。

    然后给定点权,表示在这个点挖水井需要的代价。

    再给定边权,为建造无向边i,j的代价。

    然后求怎样弄最小的代价使得前N个点,就是寺庙都能从挖的井里得到水。

解析:因每个寺庙只需找到一口井,也就是寺庙的位置到井的位置只需一条路,所以我们的最后的最优解一定得到的是一棵树,可以增加一个0点来连接所有的点,其边权就是挖井的花费,所以最终以0点做为树的根节点,这样只要是到达0点则说明井己挖好。因任意两个寺庙只有两种情况(到同一口井或不同的井),到同一口井时可能最近公共父节点到井相隔几个点。不同井则最近公共父节点一定是0点。
DP[寺庙的组成状态][子树的根节点]:最小花费。
dp[ j ] [ i ] =min{ dp[ j ] [ i ] ,dp[ k ] [ i ] +dp[ l ] [ i ]  },其中k和l是对j的一个划分。
dp[ j ] [ i ] =min{ dp[ j ] [ i ] ,dp[ j ] [ i' ] +w[ i' ] [ i ] },其中i和i'之间有边相连。
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;

#define move(a) (1<<(a))
const int N = 1010;
const int inf = 0x3fffffff;
struct EDG
{
    int v,c;
};

int n,m,dp[1<<7][N],dis[N][N];
vector<EDG>map[N];

void spfa()//求两点之间的最短距离
{
    int inq[N]={0},ts,k;
    queue<int>q;

    for(int s=0;s<=n+m;s++)
    {
        for(int j=0;j<=n+m;j++)
        dis[s][j]=inf;
        dis[s][s]=0;
        q.push(s);
        while(!q.empty())
        {
            ts=q.front(); q.pop();
            inq[ts]=0;
            k=map[ts].size();
            for(int j=0;j<k;j++)
            {
                int v=map[ts][j].v;
                if(dis[s][v]>dis[s][ts]+map[ts][j].c)
                {
                    dis[s][v]=dis[s][ts]+map[ts][j].c;
                    if(!inq[v])
                    q.push(v),inq[v]=1;
                }
            }
        }
    }
}
void countDp()
{
    for(int sta=1;sta<move(n+1);sta++)
    for(int i=0;i<=n+m;i++)
    dp[sta][i]=inf;

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

    for(int sta=1;sta<move(n+1);sta++)
    if(sta&(sta-1))
    {
        for(int i=0;i<=n+m;i++)
        {
            for(int s=sta;s>0;s=(s-1)&sta)
            if(dp[sta][i]>dp[sta^s][i]+dp[s][i])
            dp[sta][i]=dp[sta^s][i]+dp[s][i];
        }
        for(int i=0;i<=n+m;i++)
        for(int j=0;j<=n+m;j++)
            if(dp[sta][i]>dp[sta][j]+dis[j][i])
            dp[sta][i]=dp[sta][j]+dis[j][i];
    }
}
int main()
{
    int p,a,b;
    EDG e;
    while(scanf("%d%d%d",&n,&m,&p)>0)
    {
        for(int i=0;i<=n+m;i++)
        map[i].clear();

        for(int i=1;i<=n+m;i++)
        {
            scanf("%d",&e.c);
            e.v=i; map[0].push_back(e);//用挖井的花费作为i-->0边的花费,所以最终1~n点都要归于一点0
            e.v=0; map[i].push_back(e);
        }
        while(p--)
        {
            scanf("%d%d%d",&a,&b,&e.c);
            e.v=b; map[a].push_back(e);
            e.v=a; map[b].push_back(e);
        }
        spfa();
        countDp();
        printf("%d\n",dp[move(n+1)-1][0]);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值