poj 2152 Fire 树形DP

传送门


题目大意:
Z国有n个城市,从1到n给这些城市编号。城市之间连着高速公路,并且每两个城市之间有且只有一条通路。不同的高速公路可能有不同的长度。
最近Z国经常发生火灾,所以当地政府决定在某些城市修建一些消防站。在城市k修建一个消防站须要花费大小为A的费用。函数A对于不同的城市可能有不同的取值。
如果在城市k没有消防站,那么它到离它最近的消防站的距离不能超过B。每个城市在不超过距离B的前提下,必须选择最近的消防站作为负责站。函数B对于不同的城市可能有不同的取值。
为了节省钱,当地政府希望你用最少的总费用修建一些消防站,并且使得这些消防站满足上述的要求。
—–by lyd学长的PPT


分析:
f[i]代表以i为根的子树所需最小费用
D[i][j]代表i被j覆盖所需要的最小费用
dis[i][j]代表ij的距离
首先,我们要满足dis[i][j]<=d[i]才可以保证i被j覆盖
所以
if(dis[i][j]>d[i])
D[i][j]=inf
然后我们枚举j来更新root
D[i,j]=cost[j]+∑Min{ D[to[i],j]-cost[j] , f[to[i]] },dis[i,j]<=d[i]
如果to[i]没有被j覆盖,那么直接取f[to[i]]更新即可
如果to[i]]被j覆盖,我们就要减去cost[j](因为你已经建过一遍j了,但是你如果想再建一遍我也不介意>_< >o<)
f[root]=min(D[i][j]);


代码如下:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1000+5;
int cas,n,cnt,cost[maxn],d[maxn],hd[maxn],to[maxn*2],nxt[maxn*2],w[maxn*2],f[maxn],D[maxn][maxn],dis[maxn][maxn];
void add(int x,int y,int z){
    to[cnt]=y;
    w[cnt]=z;
    nxt[cnt]=hd[x];
    hd[x]=cnt++;
    dis[x][y]=dis[y][x]=z;
}
void calc(int root,int fa,int now){
    for(int i=hd[root];i!=-1;i=nxt[i]){
        if(to[i]==fa)
            continue;
        dis[now][to[i]]=dis[now][root]+w[i];
        calc(to[i],root,now);
    }
}
void dfs(int root,int fa){
    int ans=inf;
    for(int i=hd[root];i!=-1;i=nxt[i])
        if(to[i]!=fa)
            dfs(to[i],root);
    for(int j=1;j<=n;j++){
        if(dis[root][j]>d[root]){
            D[root][j]=inf;
            continue;
        }
        D[root][j]=cost[j];
        int temp=0;
        for(int i=hd[root];i!=-1;i=nxt[i]){
            if(to[i]==fa)
                continue;
            D[root][j]+=min(D[to[i]][j]-cost[j],f[to[i]]);
        }
        f[root]=min(D[root][j],f[root]);
    }
}
signed main(void){
    scanf("%d",&cas);
    while(cas--){
        cnt=0;
        scanf("%d",&n);
        memset(f,inf,sizeof(f));
        memset(hd,-1,sizeof(hd));
        memset(dis,0,sizeof(dis));
        for(int i=1;i<=n;i++)
            scanf("%d",&cost[i]);
        for(int i=1;i<=n;i++)
            scanf("%d",&d[i]);
        for(int i=1,a,b,c;i<n;i++)
            scanf("%d%d%d",&a,&b,&c),add(a,b,c),add(b,a,c);
        for(int i=1;i<=n;i++)
            dis[i][i]=0,calc(i,-1,i);
        dfs(1,-1);
        cout<<f[1]<<endl;
    }
    return 0;
}

by >o< neighthorn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值