[NOIP2015 提高組] 運輸計劃(二分 + lca + 樹上差分)

Zhang_Wenjie發表於2024-06-19

link

求操作後的所有路徑中邊權和最長的邊的最小值,二分

考慮 對答案的最短時間二分,每次得到一個二分值 mid

顯然,我們主要關心 路徑和比 mid 大的運輸路徑,這裡要維護樹上兩點間的路徑,所以要用到 lca,同時可以記錄每個點的距離(1 -> x)\(dist[x]\),這樣可以用邊字首和直接得到路徑 x -> y 和:

\[res = dist[x] + dist[y] - 2* dist[~lca(x, y)~] \]

操作只能做一次,貪心地考慮肯定是要刪掉 任意一個經過所有 \(res > mid\) 的運輸路徑的邊的集合中邊權 \(\geq \max\{res_i - mid\}\) 的邊

也就是個判定性的問題

  • 一定要經過所有運輸路徑,如果刪去的不是經過所有的,則肯定還有運輸路徑的時間要 > mid

  • 刪去邊的邊權大於等於最大差值即可,剩下的顯然都會被覆蓋,也就是不會超過 mid

怎麼知道這個邊經過了幾條路徑呢?

那麼要對路徑上所有的邊 +1,記錄覆蓋,很容易想到用 樹上差分 維護即可。

時間複雜度為 \(O(n\log (n\cdot t_i))\)


做這題還有一個小心得(也不算吧,

在實現樹上差分的還原時,可以選擇再 dfs 一遍,也可以在 lca 初始化樹的時候開一個陣列記錄搜尋序,後邊倒序迴圈一遍求字首和。

我覺得以後還是用 後者更好,因為我老是把兩次的 dfs 函式名打混,時不時就調 n 年 ...

#include <bits/stdc++.h>
#define re register int 
#define max(x, y) (x > y ? x : y)

using namespace std;
const int N = 3e5 + 10, logN = 50;

struct edge
{
	int to, w, next;
}e[N << 1];
struct road
{
	int x, y, p;
}a[N];
int top, h[N];
int dep[N], dist[N], f[N][logN], d[N];
int n, m;

int seq[N], idx;

inline void add(int x, int y, int w)
{
	e[++ top] = (edge){y, w, h[x]};
	h[x] = top;
}

void dfs(int u, int fa)
{
	dep[u] = dep[fa] + 1;
	seq[++ idx] = u;
	
	f[u][0] = fa;
	for (re i = 1; i <= log2(n); i ++)
		f[u][i] = f[f[u][i - 1]][i - 1];
		
	for (re i = h[u]; i; i = e[i].next)
	{
		int v = e[i].to, w = e[i].w;
		
		if (v == fa) continue;
		
		dist[v] = dist[u] + w;
		dfs(v, u);
	}
}

inline int lca(int u, int v)
{
	if (dep[u] < dep[v]) swap(u, v);
	
	for (re i= log2(n); i >= 0; i --)
		if (dep[f[u][i]] >= dep[v]) u = f[u][i];
		
	if (u == v) return u;
	
	for (re i = log2(n); i >= 0; i --)
		if (f[u][i] != f[v][i]) u = f[u][i], v = f[v][i];
	
	return f[u][0];
}
/*
void cale(int u, int fa)
{
	for (re i = h[u]; i; i = e[i].next)
	{
		int v = e[i].to;
		
		if (v == fa) continue;
		
		cale(v, u);
		d[u] += d[v];
	}
}
*/
inline bool check(int mid)
{
	memset(d, 0, sizeof(d));
	int cnt = 0, maxt = 0;
	
	for (re i = 1; i <= m; i ++)
	{
		int x = a[i].x, y = a[i].y, p = a[i].p;
		int t = dist[x] + dist[y] - dist[p] * 2;
		
		if (t > mid)
		{
			d[x] += 1;
			d[y] += 1;
			d[p] -= 2;
			cnt ++;	
			maxt = max(maxt, t - mid);
		}
	}
	if (!cnt) return true;
	
//	cale(1, 0);

	for (re i = n; i >= 1; i --)
	{
		int x = seq[i];
		d[f[x][0]] += d[x];
	}

	for (re i = 2; i <= n; i ++)	
		if (d[i] == cnt && dist[i] - dist[f[i][0]] >= maxt) return true;
		
	return false;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
	cin >> n >> m;
	for (re i = 1; i < n; i ++)
	{
		int x, y, w; cin >> x >> y >> w;
		add(x, y, w); add(y, x, w);
	}
	dfs(1, 0);
	for (re i = 1; i <= m; i ++)
	{
		int x, y; cin >> x >> y;
		a[i] = (road){x, y, lca(x, y)};
	}
	
	int l = 0, r = 3e8;
	while (l < r)
	{
		int mid = (l + r) / 2;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << l << '\n';
	
	return 0;
}

相關文章