1. 程式人生 > >【Codeforces827D/CF827D】Best Edge Weight(最小生成樹性質+倍增/樹鏈剖分+線段樹)

【Codeforces827D/CF827D】Best Edge Weight(最小生成樹性質+倍增/樹鏈剖分+線段樹)

題目

分析

倍增神題……(感謝T*C神犇給我講qwq)

這道題需要考慮最小生成樹的性質。首先隨便求出一棵最小生成樹,把樹邊和非樹邊分開處理。

首先,對於非樹邊(u,v)(u,v)(表示一條兩端點為uuvv的邊,下同)。考慮Kruskal演算法的過程,它必定成為樹邊的充要條件是它的權值小於樹上uuvv之間的路徑上的某條邊ee,這樣就會選中這條邊來連線uuvv所在的連通塊而不是選中ee。因此,非樹邊的答案就是它兩端點之間樹上路徑上最大邊的權值減11(如果相等則兩條邊都可以選,不符合“必定成為樹邊”)。

然後,考慮對於樹邊(u,v)(u,v)的情況。把上面的情況反過來考慮,如果有一條非樹邊(

a,b)(a,b),樹上aabb的路徑要經過(u,v)(u,v),那麼只有當任意這樣的(a,b)(a,b)的權值都大於(u,v)(u,v)時,(u,v)(u,v)才必定不會被別的邊替換下來,也就是必定成為樹邊。因此,樹邊的答案就是所有上述(a,b)(a,b)中權值最小的邊的權值減11

那麼,對於非樹邊(u,v,w)(u,v,w),我們要查詢(u,v)(u,v)間路徑上的最大邊權,並且需要用ww來更新這段路徑上所有樹邊的答案(即把這些邊的答案與ww取最小值)。注意這兩種操作是互不干擾的,不要混淆……

那麼這明顯就是個樹剖+線段樹裸題了。區間查最大值和區間修改為最小值都是線段樹的基本操作,文末有程式碼,不再贅述。

好現在開始隆重介紹某位神仙給我講的倍增做法,比樹剖+線段樹少一個lognlogn。以下為了方便敘述,預設11號點為樹根,fa[a][i]fa[a][i]表示aa點往上走2i2^i步所到的點。

區間查最大值也是倍增的經典應用,不必多言。區間取最小是這個演算法最精妙之處。考慮用minn[a][i]minn[a][i]表示所有一端是aa及其子樹,另一端是fa[a][i]fa[a][i]及其祖先的非樹邊的最小權值。mi

nn[a][0]minn[a][0]就是aaaa的父親之間的邊的答案。那麼,當我們更新minn[a][i]minn[a][i]時,也要嘗試更新minn[a][i1]minn[a][i-1]minn[fa[a][i1][i1]minn[fa[a][i-1][i-1](覆蓋了大區間的非樹邊一定會覆蓋該區間的子區間),這是一個遞迴的過程。然而每次修改都遞迴一次的複雜度是O(n)O(n)的(最壞會被卡到深度為lognlogn的滿二叉樹),O(nm)O(nm)顯然是過不去的。

但是我們要注意兩件事。第一,minn[a][i]minn[a][i]只會變小不會變大,且修改之間不會互相影響;第二,全部非樹邊考慮完後才會查詢。基於這兩件事,我們可以全部修改儘可能大的區間,最後再一起遞迴下去。這個操作類似於線段樹的pushdown。這一段非常重要和巧妙,單獨給出程式碼。(注意要分層處理,所以jj應當在外層迴圈)

for (int j = B - 1; j > 0; j--)
	for (int i = 1; i <= n; i++)
	{
		minn[i][j - 1] = min(minn[i][j - 1], minn[i][j]);
		minn[fa[i][j - 1]][j - 1] = min(minn[fa[i][j - 1]][j - 1], minn[i][j]);
	}

本題其餘細節詳見下方的程式碼。

程式碼:

法1:倍增(143143行,561561ms,6520065200KB)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

namespace zyt
{
	const int N = 2e5 + 10, M = 2e5 + 10, B = 20, INF = 0x3f3f3f3f;
	int n, m;
	struct ed
	{
		int u, v, w, id;
		bool in_tree;
		bool operator < (const ed &b) const
		{
			return w < b.w;	
		}
	}e[M];
	struct edge
	{
		int to, w, id;
	};
	vector<edge> g[N];
	int p[N], pre[N], dep[N], maxx[N][B], minn[N][B], fa[N][B], ans[M];
	int f(const int x)
	{
		return x == p[x] ? x : p[x] = f(p[x]);	
	}
	void dfs(const int u, const int f)
	{
		dep[u] = dep[f] + 1;
		fa[u][0] = f;
		for (int i = 1; i < B; i++)
		{
			fa[u][i] = fa[fa[u][i - 1]][i - 1];
			maxx[u][i] = max(maxx[u][i - 1], maxx[fa[u][i - 1]][i - 1]);
		}
		for (int i = 0; i < g[u].size(); i++)
		{
			int v = g[u][i].to;
			if (v == f)
				continue;
			pre[v] = g[u][i].id;
			maxx[v][0] = g[u][i].w;
			dfs(v, u);
		}
	}
	int query_max(int a, int b)
	{
		int ans = 0;
		if (dep[a] < dep[b])
			swap(a, b);
		for (int i = B - 1; i >= 0; i--)
			if (dep[fa[a][i]] >= dep[b])
				ans = max(ans, maxx[a][i]), a = fa[a][i];
		if (a == b)
			return ans;
		for (int i = B - 1; i >= 0; i--)
			if (fa[a][i] != fa[b][i])
			{
				ans = max(ans, max(maxx[a][i], maxx[b][i]));
				a = fa[a][i], b = fa[b][i];	
			}
		return max(ans, max(maxx[a][0], maxx[b][0]));
	}
	void change_min(int a, int b, const int w)
	{
		if (dep[a] < dep[b])
			swap(a, b);
		for (int i = B - 1; i >= 0; i--)
			if (dep[fa[a][i]] >= dep[b])
				minn[a][i] = min(minn[a][i], w), a = fa[a][i];
		if (a == b)
			return;
		for (int i = B - 1; i >= 0; i--)
			if (fa[a][i] != fa[b][i])
			{
				minn[a][i] = min(minn[a][i], w);
				minn[b][i] = min(minn[b][i], w);
				a = fa[a][i];
				b = fa[b][i];	
			}
		minn[a][0] = min(minn[a][0], w);
		minn[b][0] = min(minn[b][0], w);
	}
	void Kruskal()
	{
		for (int i = 1; i <= n; i++)
			p[i] = i;
		for (int i = 0; i < m; i++)
		{
			int u = e[i].u, v = e[i].v, x = f(u), y = f(v);
			if (x != y)
			{
				p[x] = y;
				g[u].push_back((edge){v, e[i].w, e[i].id});
				g[v].push_back((edge){u, e[i].w, e[i].id});
				e[i].in_tree = true;
			}
		}
	}
	int work()
	{
		ios::sync_with_stdio(false);
		cin.tie(0);
		cin >> n >> m;
		memset(minn, INF, sizeof(int[n + 1][B]));
		for (int i = 0; i < m; i++)
		{
			cin >> e[i].u >> e[i].v >> e[i].w;
			e[i].id = i;
		}
		sort(e, e + m);
		Kruskal();
		dfs(1, 0);
		for (int i = 0; i < m; i++)
			if (!e[i].in_tree)
			{
				ans[e[i].id] = query_max(e[i].u, e[i].v) - 1;
				change_min(e[i].u, e[i].v, e[i].w);
			}
		for (int j = B - 1; j > 0; j--)
			for (int i = 1; i <= n; i++)
			{
				minn[i][j - 1] = min(minn[i][j - 1], minn[i][j]);
				minn[fa[i][j - 1]][j - 1] = min(minn[fa[i][j - 1]][j - 1], minn