【Codeforces827D/CF827D】Best Edge Weight(最小生成树性质+倍增/树链剖分+线段树)

题目

Codeforces827D

分析

倍增神题……(感谢T*C神犇给我讲qwq)

这道题需要考虑最小生成树的性质。首先随便求出一棵最小生成树,把树边和非树边分开处理。

首先,对于非树边((u,v))(表示一条两端点为(u)(v)的边,下同)。考虑Kruskal算法的过程,它必定成为树边的充要条件是它的权值小于树上(u)(v)之间的路径上的某条边(e),这样就会选中这条边来连接(u)(v)所在的连通块而不是选中(e)。因此,非树边的答案就是它两端点之间树上路径上最大边的权值减(1)(如果相等则两条边都可以选,不符合“必定成为树边”)。

然后,考虑对于树边((u,v))的情况。把上面的情况反过来考虑,如果有一条非树边((a,b)),树上(a)(b)的路径要经过((u,v)),那么只有当任意这样的((a,b))的权值都大于((u,v))时,((u,v))才必定不会被别的边替换下来,也就是必定成为树边。因此,树边的答案就是所有上述((a,b))中权值最小的边的权值减(1)

那么,对于非树边((u,v,w)),我们要查询((u,v))间路径上的最大边权,并且需要用(w)来更新这段路径上所有树边的答案(即把这些边的答案与(w)取最小值)。注意这两种操作是互不干扰的,不要混淆……

那么这明显就是个树剖+线段树裸题了。区间查最大值和区间修改为最小值都是线段树的基本操作,文末有代码,不再赘述。

好现在开始隆重介绍某位神仙给我讲的倍增做法,比树剖+线段树少一个(logn)。以下为了方便叙述,默认(1)号点为树根,(fa[a][i])表示(a)点往上走(2^i)步所到的点。

区间查最大值也是倍增的经典应用,不必多言。区间取最小是这个算法最精妙之处。考虑用(minn[a][i])表示所有一端是(a)及其子树,另一端是(fa[a][i])及其祖先的非树边的最小权值。(minn[a][0])就是(a)(a)的父亲之间的边的答案。那么,当我们更新(minn[a][i])时,也要尝试更新(minn[a][i-1])(minn[fa[a][i-1][i-1])(覆盖了大区间的非树边一定会覆盖该区间的子区间),这是一个递归的过程。然而每次修改都递归一次的复杂度是(O(n))的(最坏会被卡到深度为(logn)的满二叉树),(O(nm))显然是过不去的。

但是我们要注意两件事。第一,(minn[a][i])只会变小不会变大,且修改之间不会互相影响;第二,全部非树边考虑完后才会查询。基于这两件事,我们可以全部修改尽可能大的区间,最后再一起递归下去。这个操作类似于线段树的pushdown。这一段非常重要和巧妙,单独给出代码。(注意要分层处理,所以(j)应当在外层循环)

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:倍增((143)行,(561)ms,(65200)KB)

#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[i][j]);
			}
		for (int i = 2; i <= n; i++)
			ans[pre[i]] = minn[i][0] - 1;
		for (int i = 0; i < m; i++)
			if (ans[i] < INF - 1)
				cout << ans[i] << ' ';
			else
				cout << "-1 ";
		return 0;
	}		
}
int main()
{
	return zyt::work();	
}

法2:树链剖分+线段树((230)行,(826)ms,(34600)KB)

(我很傻地第一次漏了size[u]+=size[v]搞成了随机剖分还以为是因为卡常所以T……菜死我了)

#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], pre_w[N], dep[N], w[N], son[N], size[N], dfn[N], top[N], fa[N], ans[N], cnt;
	int f(const int x)
	{
		return x == p[x] ? x : p[x] = f(p[x]);	
	}
	namespace Tree_Chain_Cut
	{
		void dfs1(const int u, const int f)
		{
			dep[u] = dep[f] + 1;
			fa[u] = f;
			size[u] = 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;
				pre_w[v] = g[u][i].w;
				dfs1(v, u);
				if (size[v] > size[son[u]])
					son[u] = v;
				size[u] += size[v];
			}
		}
		void dfs2(const int u, const int t)
		{
			dfn[u] = ++cnt;
			w[dfn[u]] = pre_w[u];
			top[u] = t;
			if (son[u])
				dfs2(son[u], t);
			for (int i = 0; i < g[u].size(); i++)
			{
				int v = g[u][i].to;
				if (dfn[v])
					continue;
				dfs2(v, v);
			}
		}
		namespace Segment_Tree
		{
			struct node
			{
				int minn, maxx, tag;	
			}tree[N * 4];
			inline void update(const int rot)
			{
				tree[rot].minn = min(tree[rot << 1].minn, tree[rot << 1 | 1].minn);
				tree[rot].maxx = max(tree[rot << 1].maxx, tree[rot << 1 | 1].maxx);
			}
			inline void pushdown(const int rot)
			{
				int tag = tree[rot].tag;
				tree[rot << 1].minn = min(tree[rot << 1].minn, tag);
				tree[rot << 1].tag = min(tree[rot << 1].tag, tag);
				tree[rot << 1 | 1].minn = min(tree[rot << 1 | 1].minn, tag);
				tree[rot << 1 | 1].tag = min(tree[rot << 1 | 1].tag, tag);
				tree[rot].tag = INF;
			}
			void build(const int rot, const int lt, const int rt)
			{
				tree[rot].minn = tree[rot].tag = INF;
				if (lt == rt)
				{
					tree[rot].maxx = w[lt];
					return;
				}
				int mid = (lt + rt) >> 1;
				build(rot << 1, lt, mid);
				build(rot << 1 | 1, mid + 1, rt);
				update(rot);
			}
			void change_min(const int rot, const int lt, const int rt, const int ls, const int rs, const int w)
			{
				if (ls <= lt && rt <= rs)
				{
					tree[rot].minn = min(tree[rot].minn, w);
					tree[rot].tag = min(tree[rot].tag, w);
					return;	
				}	
				pushdown(rot);
				int mid = (lt + rt) >> 1;
				if (ls <= mid)
					change_min(rot << 1, lt, mid, ls, rs, w);
				if (rs > mid)
					change_min(rot << 1 | 1, mid + 1, rt, ls, rs, w);
				update(rot);
			}
			int query_max(const int rot, const int lt, const int rt, const int ls, const int rs)
			{
				if (ls <= lt && rt <= rs)
					return tree[rot].maxx;
				pushdown(rot);
				int mid = (lt + rt) >> 1, ans = 0;
				if (ls <= mid)
					ans = max(ans, query_max(rot << 1, lt, mid, ls, rs));
				if (rs > mid)
					ans = max(ans, query_max(rot << 1 | 1, mid + 1, rt, ls, rs));
				return ans;
			}
			int query_min(const int rot, const int lt, const int rt, const int pos)
			{
				if (lt == rt)
					return tree[rot].minn;
				pushdown(rot);
				int mid = (lt + rt) >> 1, ans = 0;
				if (pos <= mid)
					return query_min(rot << 1, lt, mid, pos);
				else
					return query_min(rot << 1 | 1, mid + 1, rt, pos);
				return ans;
			}
		}
		int query_max(int a, int b)
		{
			int ans = 0;
			while (top[a] != top[b])
			{
				if (dep[top[a]] < dep[top[b]])
					swap(a, b);
				ans = max(ans, Segment_Tree::query_max(1, 1, n, dfn[top[a]], dfn[a]));
				a = fa[top[a]];
			}	
			if (a != b)
			{
				if (dep[a] < dep[b])
					swap(a, b);
				ans = max(ans, Segment_Tree::query_max(1, 1, n, dfn[b] + 1, dfn[a]));
			}
			return ans;
		}
		int query_min(const int a)
		{
			return Segment_Tree::query_min(1, 1, n, dfn[a]);
		}
		void change_min(int a, int b, const int w)
		{
			while (top[a] != top[b])
			{
				if (dep[top[a]] < dep[top[b]])
					swap(a, b);
				Segment_Tree::change_min(1, 1, n, dfn[top[a]], dfn[a], w);
				a = fa[top[a]];
			}	
			if (a != b)
			{
				if (dep[a] < dep[b])
					swap(a, b);
				Segment_Tree::change_min(1, 1, n, dfn[b] + 1, dfn[a], 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()
	{
		using namespace Tree_Chain_Cut;
		ios::sync_with_stdio(false);
		cin.tie(0);
		cin >> n >> m;
		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();
		dfs1(1, 0);
		dfs2(1, 1);
		Segment_Tree::build(1, 1, n);
		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 i = 2; i <= n; i++)
			ans[pre[i]] = query_min(i) - 1;
		for (int i = 0; i < m; i++)
			if (ans[i] < INF - 1)
				cout << ans[i] << ' ';
			else
				cout << "-1 ";
		return 0;
	}		
}
int main()
{
	return zyt::work();	
}
原文地址:https://www.cnblogs.com/zyt1253679098/p/9852648.html