LOJ 2485 「CEOI2017」Chase
題目大意
似乎壓縮起來有點困難,所以就不壓縮了吧 QwQ…
分析
考慮扔一個磁鐵能夠產生的讓逃亡者和追逐者之間的差異。
這個差異就是這個節點的所有兒子的點權之和(因爲父親和它的貢獻已經被計算過一次了)。
不難設狀態爲在以爲根的子樹上的某個節點出發,扔個磁鐵,最終停留在能夠產生的最大貢獻。另設爲與相鄰的點的點權和,爲的點權。
不難列出狀態轉移方程:
的初值爲。
由於根不固定,所以我們考慮換根。
設爲從節點向子樹內走,扔下個磁鐵所能夠產生的最大貢獻。
轉移顯然可以寫成(其中是的父親)
但是我們必須去重(有可能的路徑都選到了同一個兒子),所以我們在更新開始前就更新答案,即先做:
再對進行轉移。
考慮對於子樹中的兩個節點,一種情況是從走上再走下,另一種情況是從走上再走下,這兩種情況得到的答案是很有可能不一樣的。
而在我們剛纔的轉移中,由於爲了防止路徑重複先更新了答案,這表明我們所得到的選取的路徑一定是在選取的路徑的左邊。也就是說我們必須統計在左邊的情況。
一種比較有效的方法是將壓進一個棧裏,再從棧中取出,一個個更新即可。
注意答案還需和進行比較
參考代碼
#include <stack>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int Maxn = 1e5;
const int Maxv = 100;
int N, V;
int A[Maxn + 5];
vector<int> G[Maxn + 5];
void addedge(int u, int v) {
G[u].push_back(v);
G[v].push_back(u);
}
ll val[Maxn + 5];
ll f1[Maxn + 5][Maxv + 5], f2[Maxn + 5][Maxv + 5];
ll ans;
void calc(int x, int y, int z) {
for(int i = 1; i <= V; i++)
ans = max(ans, f1[x][i] + f2[y][V - i]);
for(int i = 1; i <= V; i++) {
f1[x][i] = max(f1[x][i], max(f1[y][i], f1[y][i - 1] + val[x] - A[y]));
f2[x][i] = max(f2[x][i], max(f2[y][i], f2[y][i - 1] + val[x] - A[z]));
}
}
void DFS(int u, int fa) {
for(int i = 1; i <= V; i++)
f1[u][i] = val[u], f2[u][i] = val[u] - A[fa];
for(int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if(v == fa) continue;
DFS(v, u);
calc(u, v, fa);
}
stack<int> stk;
for(int i = 1; i <= V; i++)
f1[u][i] = val[u], f2[u][i] = val[u] - A[fa];
for(int i = 0; i < (int)G[u].size(); i++){
int v = G[u][i];
if(v == fa) continue;
stk.push(v);
}
while(!stk.empty()) {
int v = stk.top();
stk.pop(), calc(u, v, fa);
}
ans = max(ans, max(f1[u][V], f2[u][V]));
}
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d %d", &N, &V);
for(int i = 1; i <= N; i++)
scanf("%d", &A[i]);
for(int i = 1; i < N; i++) {
int u, v;
scanf("%d %d", &u, &v);
addedge(u, v);
val[u] += A[v], val[v] += A[u];
}
DFS(1, 0);
printf("%lld\n", ans);
return 0;
}