LOJ #2478.「九省聯考 2018」林克卡特樹
題目大意
給定一棵有負權邊的樹,現在必須恰好刪去條邊,並加上恰好條權值爲的邊,要求最大化它的直徑長度。
分析
考慮連上條權值爲的邊的意義:
似乎並沒有什麼意義。只是將一條直徑拆成了條鏈帶權的鏈而已。
然而我們也可以用這個方法將原問題轉化爲求解條點不相交的鏈的最大邊權和。
爲了描述方便,我們強制定義一個點也屬於一條鏈。(可以看做它形成了一個自環)
設狀態表示以爲根的子樹中,選出條點不相交的鏈的最大邊權和,且的度數爲,且強制每個點對應它到父親的邊。
我們嘗試列出幾個狀態轉移方程:(其中是的兒子)
- :當的度數爲時,可知這個點一定不在鏈上,直接與子樹合併答案即可。
- :當的度數爲時,這時是某條鏈的端點,我們可以將它分成自己本身有一條鏈、兒子有一條鏈兩種情況,分別取最大即可。
- :這時在某條鏈的中部。我們可以認爲這種情況可以由分別是兩條點不相交的鏈的端點或者在某條鏈中部,在另外一條鏈上所形成的。
- :把答案合併起來。
於是答案就是
然而這個算法是的,顯然過不了。。。
考慮優化:
我們令爲橫座標,求得的最優解爲縱座標,然後 打表 發現這個東西有凸性。於是果斷上帶權二分。
我們給每條路徑二分一個附件權值,在新增一條鏈時減掉,這樣,在較大時可以選出更多的點不相交的鏈出來,較小時可以選出更少的點不相交的鏈出來。於是我們二分這個,二分到我們分出的鏈恰好有條出來,最後計算答案時再加回即可。
而如何計算這個鏈的數量,我們可以用上面那個樹形 DP 。只不過將最後一維去掉,並在 DP 時統計鏈的數量即可。
由於有負權邊,故二分的範圍要弄大一點。
參考代碼
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int Maxn = 3e5;
const ll INF = 1E12;
int N, K;
vector<pair<int, int> > G[Maxn + 5];
void addedge(int u, int v, int w) {
G[u].push_back(make_pair(v, w));
G[v].push_back(make_pair(u, w));
}
struct State {
ll val;
int cnt;
State(){}
State(ll a, int b) {
val = a, cnt = b;
}
State operator + (const State &rhs) {
return State(val + rhs.val, cnt + rhs.cnt);
}
bool operator < (const State &rhs) const {
return val == rhs.val ? cnt > rhs.cnt : val < rhs.val;
}
};
State f[3][Maxn + 5];
void DFS(int u, int fa, ll val) {
for(int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i].first;
ll w = G[u][i].second;
if(v == fa) continue;
DFS(v, u, val);
f[2][u] = max(f[2][u] + f[0][v], f[1][u] + f[1][v] + State(w - val, 1));
f[1][u] = max(f[1][u] + f[0][v], f[0][u] + f[1][v] + State(w, 0));
f[0][u] = f[0][u] + f[0][v];
}
f[0][u] = max(f[0][u], max(f[1][u] + State(-val, 1), f[2][u]));
}
bool check(ll val) {
for(int i = 1; i <= N; i++) {
f[0][i] = f[1][i] = State(0, 0);
f[2][i] = State(-val, 1);
}
DFS(1, 0, val);
return f[0][1].cnt >= K + 1;
}
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d %d", &N, &K);
for(int i = 1; i < N; i++) {
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
addedge(u, v, w);
}
ll lb = -INF, ub = INF;
while(lb <= ub) {
ll mid = (lb + ub) >> 1;
if(check(mid)) lb = mid + 1;
else ub = mid - 1;
}
check(lb);
ll ans = f[0][1].val + 1LL * lb * (K + 1);
printf("%lld\n", ans);
return 0;
}