Fire
Description
Country Z has N cities, which are numbered from 1 to N. Cities are connected by highways, and there is exact one path between two different cities. Recently country Z often caught fire, so the government decided to build some firehouses in some cities. Build a firehouse in city K cost W(K). W for different cities may be different. If there is not firehouse in city K, the distance between it and the nearest city which has a firehouse, can’t be more than D(K). D for different cities also may be different. To save money, the government wants you to calculate the minimum cost to build firehouses.
Input
The first line of input contains a single integer T representing the number of test cases. The following T blocks each represents a test case.
The first line of each block contains an integer N (1 < N <= 1000). The second line contains N numbers separated by one or more blanks. The I-th number means W(I) (0 < W(I) <= 10000). The third line contains N numbers separated by one or more blanks. The I-th number means D(I) (0 <= D(I) <= 10000). The following N-1 lines each contains three integers u, v, L (1 <= u, v <= N,0 < L <= 1000), which means there is a highway between city u and v of length L. Output
For each test case output the minimum cost on a single line.
Sample Input 5 5 1 1 1 1 1 1 1 1 1 1 1 2 1 2 3 1 3 4 1 4 5 1 5 1 1 1 1 1 2 1 1 1 2 1 2 1 2 3 1 3 4 1 4 5 1 5 1 1 3 1 1 2 1 1 1 2 1 2 1 2 3 1 3 4 1 4 5 1 4 2 1 1 1 3 4 3 2 1 2 3 1 3 3 1 4 2 4 4 1 1 1 3 4 3 2 1 2 3 1 3 3 1 4 2 Sample Output 2 1 2 2 3 Source
POJ Monthly,Lou Tiancheng
|
现在面对的主要障碍无疑是,“每个城市在不超过距离的前提下,必须选择最近的消防站作为负责站”这一严格限制在状态转移中起着干扰作用。其实,我们并不须要知道最近的消防站是哪个,而只要保证在距离内至少有一个消防站就足够了。于是可以尝试放宽这个限制:把这个限制转化为“每个城市在不超过距离的前提下,可以选择任意一个消防站作为负责站”。
转化后,求出的最优解与转化前的是一样的。原因在于在转化后,必定存在一个最优解满足性质“每个城市在不超过距离的前提下,必须选择最近的消防站作为负责站”。
现在每个城市都享有一定的“自由权”了,可以在自己的活动范围内自由地选择消防站作为负责站。此时就有必要把状态表示重新定义一下——令表示
① 在以为根的子树里修建一些消防站;
② 在结点必须修建一个消防站;
③ 以为根的子树内的每个结点在不超过距离的前提下,选择一个在子树内或结点上的消防站作为负责站;
④ 结点必须选择结点上的消防站作为负责站;
的最少总费用(如果在树外则不算在内)。自然而然地“最近的消防站”这几个字在定义中消失了,这为以后确定动态规划转移方程提供了很大的方便。
为了转移方便,先定义一个简单的辅助状态,表示在以为根的子树中,修建合符要求(子树中所有结点到其树内的负责站的距离不超过其对应的函数D值)的消防站的最小费用。明显地
下面对进行分析:
f[i][j]:表示以i为根的子树里修建一些消防站,并在节点j处修建一消防站,i的负责站必须是j
best[i]:表示以i为根的子树的所有节点都有负责站的最小花费
dist[i]表示i到x的距离,所以没换一个节点,都要再求一次dist[]。
好了下面说下转移方程:
首先选定一个节点(x)为根 ,遍历所有节点
如果d[x]<dist[i],则i点不可能成为x的负责站f[x][i]=INF;
如果d[x]>=dist[i],则i可以成为x的负责站,分三种情况讨论:(y是x的儿子)
A.
当i在以x为根的子树外时,对于x的每个儿子y都有两种选择:选择以y为根的子树内或外的消防站为负责站。当选择以y为根的子树内的消防站为负责站时,其子树所需的最少费用为best[y],当选择以y为根的子树外的消防站为负责站时,y只可以选择i上的消防站作为负责站,此时其子树所需的最少费用为f[k][i]。综上得到
f[x][i]+=min(best[y],f[y][i]);
(其实这块是反过来想的,当i点在x为根的子树外时,如果的x的儿子y选择i作为负责站,那么在i节点与y节点中间的x就一定是选择i了。)
B.
当x==i时,x的每个儿子的选择情况与A中的一样。此时还要加上修建x上的消防站的费用。因此
f[x][i]+=w[x]+min(best[y],f[y][i]);
C.
当x!=i并且i在以x为根的子树内时,此时i必定在x的某个儿子y的子树里。对于x的每个不是y的儿子其选择情况与⑴中的一样,而对于y,它只能选择j作为负责站。综上得到
f[x][i]+=f[y][i]+min(best[k],f[k][i]);(k是x中不是y的所有儿子)
分析不是我做的。我是看的陈启锋的论文后研究好一阵才搞出来的。
- #include<iostream>
- #include<string.h>
- #include<stdio.h>
- #include<vector>
- using namespace std;
- #define INF 0x3f3f3f3f
- struct node//定义边结构
- {
- int to;//终点
- int weight;//权重
- node(int t,int w)//初始化函数
- {
- to=t,weight=w ;
- }
- };
- vector<node> edge[1005];
- int f[1005][1005];//动态函数
- int w[1005],d[1005],best[1005],dis[1005];
- int n;
- int mi(int a,int b)
- {
- return a<b?a:b;
- }
- void dist(int f)//算各点到根f的距离
- {
- int i,len,s;
- len=edge[f].size();
- for(i=0; i<len; i++)
- {
- s=edge[f][i].to;
- if(dis[s]!=-1)//为了防止逆向访问
- continue;
- dis[s]=dis[f]+edge[f][i].weight;
- dist(s);
- }
- }
- void dfs(int fa,int x)
- {
- int i,j,len,s;
- len=edge[x].size();
- for(i=0; i<len; i++)//遍历边递归计算子树
- {
- s=edge[x][i].to;
- if(s==fa)//为了防止逆向访问
- continue;
- dfs(x,s);
- }
- memset(dis,-1,sizeof dis);
- dis[x]=0;
- dist(x);
- best[x]=INF;
- for(i=1; i<=n; i++)//枚举x的负责站
- {
- if(dis[i]<=d[x])//寻找有效站点
- {
- f[x][i]=w[i];
- for(j=0; j<len; j++)
- {
- s=edge[x][j].to;
- if(s==fa)
- continue;
- f[x][i]+=mi(best[s],f[s][i]-w[i]);//选择儿子结点也在i点建和不在i点建的较小值
- }
- if(best[x]>f[x][i])//如果儿子结点以子树外的结点为负责站则父结点也必须以那点为负责站
- best[x]=f[x][i];
- }
- else
- f[x][i]=INF;
- }
- }
- int main()
- {
- int i,t,u,v,l;
- scanf("%d",&t);
- while(t--)
- {
- scanf("%d",&n);
- for(i=1; i<=n; i++)
- scanf("%d",&w[i]);
- for(i=1; i<=n; i++)
- edge[i].clear();
- for(i=1; i<=n; i++)
- scanf("%d",&d[i]);
- for(i=1; i<n; i++)//存边的新方法
- {
- scanf("%d%d%d",&u,&v,&l);
- edge[u].push_back(node(v,l));
- edge[v].push_back(node(u,l));
- }
- dfs(0,1);
- printf("%d\n",best[1]);
- }
- return 0;
- }