JZOJ 3567. 【GDKOI2014】石油儲備計劃
阿新 • • 發佈:2020-08-13
題目
解析
多種解法:有上下界費用流(nibi),樹形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]); } }