POJ2152 Fire



描述:

Z国有N个城市,编号为从1到N。城市之间用高速公路连接,并且每两个城市之间都有唯一一条路径。最近Z国经常发生火灾,所以政府决定在一些城市修建消防站。在城市K建立消防站要花费W(K)。不同的城市花费不同。如果在城市K没有消防站,则离他最近的消防站与他的距离不能超过D(K),不同城市的D也不相同。为了省钱,政府希望你能算出修建消防站最小的总花费。

输入:

第一行输入包含一个整数T,表示有T组测试数据。

每组测试数据第一行为一整数N,第二行包含由一个或更多空格分隔开的数,第I个数表示W(I)。第三行第I个数表示D(I)。接下来的N-1行每行有三个数u,v,l,表示城市u与城市v之间有一条长为l的公路连接。

输出:

对每组测试数据,输出最小花费。


分析:

dp[i][j]表示以i为根的子树里每个节点都被消防站管理,并且城市i被城市j所建的消防站管理情况下的最小花费。

best[i]表示以i为根的子树的所有节点都被管理时的最小花费,我们的目的就是求出best[1]。而best[i]显然就是dp[i][j]中的最小值(j表示i的所有孩子)

dist[i]表示以key为中心,城市i到城市key的距离,当距离大于D(key)时,就意味着key不能由i来管理。

首先dfs到子节点,然后求出以key为中心的所有距离dist,再枚举每个节点i,考虑dp[key][i],如果dist[i]>d[key](即key能容忍消防站到他的最远距离),就将dp[key][i]置为一很大的数(M=1<<30),表示该情况不会被取到。如果能取到,则在此条件下枚举key的孩子,状态转移方程:dp[key][i]=w[i]+sum(min(best[j],dp[j][i]-w[i]))。即:城市key被城市i管理时,其花费为w[i]与min(各孩子节点的最小花费,孩子节点j被i管理的最小花费减去i的建设费用)。最后best[key]为dp[key][i]最小的一个。

代码:

#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
#define N 1005
#define M (1<<30)
#define min(x,y) ((x)<(y)?(x):(y))
struct Node{
    int v,l;
    Node(int v,int l):v(v),l(l){}
};
vector<Node> G[N];
int w[N],d[N],best[N],dp[N][N],dist[N],n;
void dfs(int key,int fa){                //考虑节点key,其父节点为fa
    int v;
    for(unsigned int i=0;i<G[key].size();++i){      //对每个孩子节点
        v=G[key][i].v;
        if(v==fa) continue;                  //排除父节点的情况,防止死循环
        dfs(v,key);                      //递归下去直到叶节点
    }
    memset(dist,-1,sizeof(dist));dist[key]=0;    //初始化dist,-1表示该节点dist值还没有被设置
    queue<int> q;q.push(key);int v1;
    while(!q.empty()){                          //bfs求dist
        v=q.front();q.pop();
        for(unsigned int i=0;i<G[v].size();++i){
            if(dist[v1=G[v][i].v]!=-1) continue;
            dist[v1]=dist[v]+G[v][i].l;q.push(v1);
        }
    }best[key]=M;
    for(int i=1;i<=n;++i){
        if(dist[i]>d[key]) dp[key][i]=M;        //排除消防站与key距离过大的情况
        else{
            dp[key][i]=w[i];                 //先要在城市i建立消防站
            for(unsigned int j=0;j<G[key].size();++j){   //考虑key的每个孩子节点
                if((v=G[key][j].v)==fa) continue;
                dp[key][i]+=min(best[v],dp[v][i]-w[i]);    //该递推式含义见上文
            }
            best[key]=min(best[key],dp[key][i]);
        }
    }
}
int main(){
    int t,u,v,l;scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;++i) G[i].clear();   //初始化不要忘了
        for(int i=1;i<=n;++i) scanf("%d",&w[i]);
        for(int i=1;i<=n;++i) scanf("%d",&d[i]);
        for(int i=1;i<n;++i){
            scanf("%d%d%d",&u,&v,&l);
            G[u].push_back(Node(v,l));
            G[v].push_back(Node(u,l));
        }
        dfs(1,0);    //从1开始,父节点默认为0
        printf("%d\n",best[1]);  
    }
    return 0;
}
反思:

DP类问题的关键就是找正确的状态,自己还是太菜,学习了。。

到底怎么才能学好DP啊QAQ,我好笨

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值