题目
解析
多种解法:有上下界费用流(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
" , f[1][num]);
}
}