描述:
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,我好笨