【Codeforces827D/CF827D】Best Edge Weight(最小生成樹性質+倍增/樹鏈剖分+線段樹)
題目
分析
倍增神題……(感謝T*C神犇給我講qwq)
這道題需要考慮最小生成樹的性質。首先隨便求出一棵最小生成樹,把樹邊和非樹邊分開處理。
首先,對於非樹邊(表示一條兩端點為和的邊,下同)。考慮Kruskal演算法的過程,它必定成為樹邊的充要條件是它的權值小於樹上到之間的路徑上的某條邊,這樣就會選中這條邊來連線和所在的連通塊而不是選中。因此,非樹邊的答案就是它兩端點之間樹上路徑上最大邊的權值減(如果相等則兩條邊都可以選,不符合“必定成為樹邊”)。
然後,考慮對於樹邊的情況。把上面的情況反過來考慮,如果有一條非樹邊,樹上和的路徑要經過,那麼只有當任意這樣的的權值都大於時,才必定不會被別的邊替換下來,也就是必定成為樹邊。因此,樹邊的答案就是所有上述中權值最小的邊的權值減。
那麼,對於非樹邊,我們要查詢間路徑上的最大邊權,並且需要用來更新這段路徑上所有樹邊的答案(即把這些邊的答案與取最小值)。注意這兩種操作是互不干擾的,不要混淆……
那麼這明顯就是個樹剖+線段樹裸題了。區間查最大值和區間修改為最小值都是線段樹的基本操作,文末有程式碼,不再贅述。
好現在開始隆重介紹某位神仙給我講的倍增做法,比樹剖+線段樹少一個。以下為了方便敘述,預設號點為樹根,表示點往上走步所到的點。
區間查最大值也是倍增的經典應用,不必多言。區間取最小是這個演算法最精妙之處。考慮用表示所有一端是及其子樹,另一端是及其祖先的非樹邊的最小權值。就是和的父親之間的邊的答案。那麼,當我們更新時,也要嘗試更新和(覆蓋了大區間的非樹邊一定會覆蓋該區間的子區間),這是一個遞迴的過程。然而每次修改都遞迴一次的複雜度是的(最壞會被卡到深度為的滿二叉樹),顯然是過不去的。
但是我們要注意兩件事。第一,只會變小不會變大,且修改之間不會互相影響;第二,全部非樹邊考慮完後才會查詢。基於這兩件事,我們可以全部修改儘可能大的區間,最後再一起遞迴下去。這個操作類似於線段樹的pushdown。這一段非常重要和巧妙,單獨給出程式碼。(注意要分層處理,所以應當在外層迴圈)
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:倍增(行,ms,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