Capturing a country
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1321 Accepted Submission(s): 542
For simplicity, we assume that there is only one path to go from one city to another city.
3 1 2 5 3 8 1 1 2 1 3
3
题意:
有一棵树,两个人 a 和 b 要去占领这棵树上的所有点,两个人占领每个点所需的花费各不相同
如果 a 占领了 i 点,那么去占领 与 i 相邻的点的时候,该点的花费减半,b 也是如此
问占领完整棵树所需最小花费
思路:
把这棵树分成几棵小的子树,每个子树只让同一个人占领
但是怎么操作呢? 用树形DP
建好树之后,从根节点往下深搜,搜到叶子节点之后开始回溯记录值
sa:表示该点取 a 的时候,且未确定起点,下面的子树的最小花费
sb:表示该点取 b 的时候,且未确定起点,下面的子树的最小花费
da:表示该点取 a 的时候,让取值最小的起点额外花费(即子树中 a 起点值的一半)
db:表示该点取 a 的时候,让取值最小的起点额外花费(即子树钟 b 起点值的一半)
dp[i][0][0]:表示 i 点取 a 且 尚未确定起点的最小花费
dp[i][0][1]:表示 i 点取 a 且 已经确定起点的最小花费
dp[i][1][0]:表示 i 点取 b 且 尚未确定起点的最小花费
dp[i][1][1]:表示 i 点取 b 且 已经确定起点的最小花费
那么:到达根节点之后 dp[i][0][0] = sa + a[i] / 2 ; 都是取一半价值
dp[i][0][1] = min(sa + a[i] , sa + a[i] / 2 + da) 该点为起点或者下面的某个点为起点
b的情况也是同上
每个点回溯后更新 sa sb
sa += min(dp[v][0][0],dp[v][1][1]) 如果该点取 a ,那么就加等于下面a未确定起点的
或者是 下面 b 已经确定起点的 ,取小的那个值
更新 da db
da = min(da,dp[v][0][1] - min(dp[v][0][0],dp[v][1][1]))
取当前点为a,且已确定起点的值,减去下面的子树为 a 为确定 或为 b已确定的 两个值中小的那个(因为小的那个才是构成当前 取a 且已确定的最优情况下的子树值)
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
using namespace std;
#define ll long long
#define mem(a,x) memset(a,x,sizeof(a))
#define maxn 105
const int inf = 1 << 30;
int n,a[maxn],b[maxn],dp[maxn][2][2];
vector<int>vec[maxn];
// dp[i][0][0]:表示 i 点取 a 且 尚未确定起点的最小花费
// dp[i][0][1]:表示 i 点取 a 且 已经确定起点的最小花费
// dp[i][1][0]:表示 i 点取 b 且 尚未确定起点的最小花费
// dp[i][1][1]:表示 i 点取 b 且 已经确定起点的最小花费
void tree_dp(int x,int pre){
int sz = vec[x].size();
int sa,sb,da,db;
sa = sb = 0;
da = db = inf;
for(int i = 0;i < sz;i++){
int v = vec[x][i];
if(v == pre)
continue;
tree_dp(v,x);
sa += min(dp[v][0][0],dp[v][1][1]); // 表示该点取 a 的时候,且未确定起点,下面的子树的最小花费
sb += min(dp[v][1][0],dp[v][0][1]);
da = min(da,dp[v][0][1] - min(dp[v][0][0],dp[v][1][1])); //更新预计起点的额外值
db = min(db,dp[v][1][1] - min(dp[v][1][0],dp[v][0][1]));
}
dp[x][0][0] = sa + a[x] / 2; // 该点取半值,未确定起点,全部取半值
dp[x][0][1] = min(sa + a[x],sa + a[x] / 2 + da); // 该点做起点 或者 该点取半值,起点取下面的别的点
dp[x][1][0] = sb + b[x] / 2;
dp[x][1][1] = min(sb + b[x],sb + b[x] / 2 + db);
}
void init(){
for(int i = 0;i <= n;i++){
vec[i].clear();
}
mem(dp,0);
}
int main(){
int u,v;
while(scanf("%d",&n) != EOF){
init();
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
}
for(int i = 1;i <= n;i++){
scanf("%d",&b[i]);
}
for(int i = 1;i < n;i++){
scanf("%d %d",&u,&v);
vec[u].push_back(v);
vec[v].push_back(u);
}
tree_dp(1,0);
int ans = min(dp[1][0][1],dp[1][1][1]);
printf("%d\n",ans);
}
return 0;
}