POJ 2152 Fire 很难的树形DP

题意:有N个城市,连接这些城市的道路组成了一个树形的结构。现在想在一些城市建立消防站。如果选择在城市i建立消防站,需要的费用是 w i   ,这个消防站负责该城市的防火工作。如果不选择建立消防站,那在距离为 d i   的范围内要有一个消防站来负责城市i的防火工作。现在想让所有城市都能保证安全,求最小的费用。
思路:想了很长时间,以为和前面的独立集是相关的,但是怎么也想不出来。。就去搜题解了。
在陈启峰的论文中《一张一弛_解题之道》有这道题的解法。
因为要利用任意两点间的距离,我们可以用 Θ(n 2 )  的方法得到这些距离。
设dp[u][i]表示以u为根的子树的每个节点都有消防站负责,且节点u的负责消防站是建立在节点i上的最小花费。
best[u]表示以u为根的子树的每个节点以u内的某个节点作为负责站的最小花费。
容易得到:

best[u]=min(dp[u][i]),isubtree(u) 

下面考虑dp[u][i]的状态转移:
1.如果 dist(u,i)>d u   ,这个时候,节点u是不能以i处的消防站为负责站,此时 dp[u][i]=+ 
2.如果 dist(u,i)<=d u   ,这个时候,节点u就能选择以i处的消防站为负责站了。对于u的儿子v,可以选择节点i作为自己的负责站,也可以用自己子树内的消防站。
dp[u][i]= vson(u) min(dp[v][i]w i ,best[v]) 

初始化条件:
dp[u][i]=c i ,dist(u,i)d u  

这样在,最后的答案就是best[1]。
注意:
1.对于best[u]的求解,我们直接在DFS的过程中求就可以了,不用刻意的标记以u为根的子树的节点。因为DFS的调用过程就反应了节点的从属关系。
代码如下:

#include <iostream>
#include <cstring>
#include <map>
#include <string>
#include <algorithm>

using namespace std;

const int MAX = 1010;

struct edge{
    int to,w;
    edge(){}
    edge(int t, int ww):to(t),w(ww){}
} edges[MAX<<1];

int N;
int nxt[MAX<<1],head[MAX],tot;
int dis[MAX][MAX],cost[MAX],lim[MAX];
int best[MAX],dp[MAX][MAX];


void init()
{
    memset(head,-1,sizeof(head));
    memset(best,0x3f,sizeof(best));
    memset(dp,0x3f,sizeof(dp));
    tot = 0;
}

void addedge(int u, int v, int w)
{
    edges[tot] = edge(v,w);
    nxt[tot] = head[u];
    head[u] = tot++;
}

void dfs(int u, int p, int di,int root)
{
    dis[root][u] = di;
    for(int i = head[u]; ~i; i = nxt[i]){
        edge & e = edges[i];
        if(e.to == p) continue;
        dfs(e.to,u,di+e.w,root);
    }
}

void solve(int u, int p)
{
    for(int i = 1; i <= N; ++i)
        if(dis[u][i] <= lim[u])
            dp[u][i] = cost[i];
    for(int i = head[u]; ~i; i = nxt[i]){
        edge & e = edges[i];
        if(e.to == p) continue;
        solve(e.to,u);
        for(int j = 1; j <= N; ++j)
            if(dis[u][j] <= lim[u])
                dp[u][j] += min(dp[e.to][j] - cost[j],best[e.to]);
    }
    for(int i = 1; i <= N; ++i)
        best[u] = min(best[u],dp[u][i]);
}



int main(void)
{
    //freopen("input.txt","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d",&N);
        for(int i = 1; i <= N; ++i)
            scanf("%d",&cost[i]);
        for(int i = 1; i <= N; ++i)
            scanf("%d",&lim[i]);
        for(int i = 1; i <= N-1; ++i){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
            addedge(v,u,w);
        }
        for(int i = 1; i <= N; ++i)
            dfs(i,0,0,i);
        solve(1,0);
        printf("%d\n",best[1]);
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值