【LOJ】【帶權二分】【樹形DP】#2478 「九省聯考 2018」林克卡特樹

LOJ #2478.「九省聯考 2018」林克卡特樹

題目大意

◇題目傳送門◆

給定一棵有負權邊的樹,現在必須恰好刪去KK條邊,並加上恰好KK條權值爲00的邊,要求最大化它的直徑長度。

分析

考慮連上KK條權值爲00的邊的意義:

似乎並沒有什麼意義。只是將一條直徑拆成了K+1K+1條鏈帶權的鏈而已。

然而我們也可以用這個方法將原問題轉化爲求解K+1K+1條點不相交的鏈的最大邊權和。

爲了描述方便,我們強制定義一個點也屬於一條鏈。(可以看做它形成了一個自環)

設狀態f(u,0/1/2,k)f(u,0/1/2,k)表示以uu爲根的子樹中,選出kk條點不相交的鏈的最大邊權和,且uu的度數爲0,1,20,1,2,且強制每個點對應它到父親的邊。

我們嘗試列出幾個狀態轉移方程:(其中vvuu的兒子)

  • f(u,0,i)=max{f(u,0,j)+f(v,0,ij)}f(u,0,i)=\max\{f(u,0,j)+f(v,0,i-j)\}:當uu的度數爲00時,可知這個點一定不在鏈上,直接與子樹合併答案即可。
  • f(u,1,i)=max{f(u,0,j)+f(v,1,ij)+wu,v,f(u,1,j)+f(v,0,ij)}f(u,1,i)=\max\{f(u,0,j)+f(v,1,i-j)+w_{u,v},f(u,1,j)+f(v,0,i-j)\}:當uu的度數爲11時,這時uu是某條鏈的端點,我們可以將它分成自己本身有一條鏈、兒子有一條鏈兩種情況,分別取最大即可。
  • f(u,2,i)=max{f(u,1,j)+f(v,1,ij1)+wu,v,f(u,2,j)+f(v,1,ij)}f(u,2,i)=\max\{f(u,1,j)+f(v,1,i-j-1)+w_{u,v},f(u,2,j)+f(v,1,i-j)\}:這時uu在某條鏈的中部。我們可以認爲這種情況可以由u,vu,v分別是兩條點不相交的鏈的端點或者uu在某條鏈中部,vv在另外一條鏈上所形成的。
  • f(u,0,i)=max{f(u,0,i),f(u,1,i1),f(u,2,i)}f(u,0,i)=\max\{f(u,0,i),f(u,1,i-1),f(u,2,i)\}:把答案合併起來。

於是答案就是f(1,0,K+1)f(1,0,K+1)

然而這個算法是O(NK2)O(NK^2)的,顯然過不了。。。

考慮優化:

我們令KK爲橫座標,求得的最優解爲縱座標,然後 打表 發現這個東西有凸性。於是果斷上帶權二分。

我們給每條路徑二分一個附件權值vv,在新增一條鏈時減掉vv,這樣,在vv較大時可以選出更多的點不相交的鏈出來,vv較小時可以選出更少的點不相交的鏈出來。於是我們二分這個vv,二分到我們分出的鏈恰好有K+1K+1條出來,最後計算答案時再加回即可。

而如何計算這個鏈的數量,我們可以用上面那個樹形 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章