JZOJ 3567. 【GDKOI2014】石油储备计划
题目

解析
多种解法:有上下界费用流(nb),树形DP等
而由于我太菜,前者待日后再补
下面介绍树形DP的解法
首先我们等发现一些性质:
最后使方差最小,树的每个点权值必然在 \([sum/n..sum/n+1]\) 之间,其中 \(sum\) 指石油总和
那么我们可不可以试试枚举最后有多少个点为 \(sum/n+1\)?
当然可以
设 \(f_{i,j}\) 表示以 \(i\) 为根的子树有 \(j\) 个点权值为 \(sum/n+1\)
那么转移时枚举它的儿子,它子树的 \(j\),它儿子子树的 \(j\)
\(f_{i,j}=f_{son,k}+f_{i,j-k}+c*|s[v] - (ave + 1)*k - ave*(sz[v] - k)|\)
\(c\) 为子树到 \(i\) 的边权,\(s_v\) 表示子树的油的总和,那么后面一截就是必须流出去或流进来的,经过 \(c\) 这条边。
注意我们枚举时是先用某一个儿子来更新原来的 \(f\),而转移时又要借助 \(father\) 子树的 \(j\),所以我们不能直接修改,而是应该在所有 \(j\) 都枚举完后再修改,具体可见代码
总共是 \(O(n^3)\)
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 105;
int n , num , sz[N] , h[N] , tot;
LL f[N][N] , g[N] , s[N] , ave;
struct edge{
int to , w , nxt;
}e[N << 1];
inline void add(int x , int y , int z){e[++tot] = edge{y , z , h[x]} , h[x] = tot;}
inline void dfs(int x , int fa)
{
sz[x] = 1;
f[x][0] = f[x][1] = 0;
for(register int i = h[x]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v == fa) continue;
dfs(v , x) , s[x] += s[v] , sz[x] += sz[v];
}
for(register int i = h[x]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v == fa) continue;
memset(g , 0x3f3f3f3f , sizeof g);
for(register int j = 0; j <= num && j <= sz[x]; j++)
for(register int k = 0; k <= j && k <= sz[v]; k++)
g[j] = min(g[j] , f[v][k] + f[x][j - k] + (LL)abs(s[v] - (ave + 1)*k - ave*(sz[v] - k)) * e[i].w);
for(register int j = 0; j <= num && j <= sz[x]; j++) f[x][j] = g[j];
}
}
int main()
{
int T;
scanf("%d" , &T);
for(; T; T--)
{
scanf("%d" , &n);
ave = 0;
for(register int i = 1; i <= n; i++) scanf("%lld" , &s[i]) , ave += s[i];
num = ave % n , ave = ave / n , tot = 0 , memset(h , 0 , sizeof h);
int x , y , z;
for(register int i = 1; i < n; i++)
{
scanf("%d%d%d" , &x , &y , &z);
add(x , y , z) , add(y , x , z);
}
memset(f , 0x3f3f3f3f , sizeof f);
dfs(1 , 0);
printf("%lld\n" , f[1][num]);
}
}

浙公网安备 33010602011771号