【题解】P4768 [NOI2018] 归程 / Kruskal 重构树
补补以前懒得总结的零碎东西。
kruskal 重构树
使用条件:求无向图中两点之间所有路径的最大边权的最小值
构造:
-
依 kruskal 得到最小生成树
-
从小到大考虑生成树中的边 \((u, v)\)
-
对于 \((u, v)\),新建一个结点,作为重构树中 \(u, v\) 的父结点
-
该结点的点权为 \((u, v)\) 的边权
性质:
-
原图中两点之间所有路径的最大边权的最小值
= 最小生成树上两点之间路径的边权最大值
= 重构树两点 lca 的点权
-
kruskal 重构树是一棵二叉树
-
kruskal 重构树以最小生成树构造时为大根堆,反之同理
-
令 \(v\) 该结点为重构树上 \(u\) 深度最浅的点权不超过 \(w\) 的祖先结点,则原图中结点 \(u\) 经过边权不超过 \(w\) 的边能到达的结点为 \(v\) 的子树
求最小边权的最大值同理。
思路
Kruskal 重构树。
首先意识到只经过海拔不超过定值的点可以通过 Kruskal 重构树的子树表示,答案即为子树中任意一个结点到 \(1\) 号结点经过的积水边总长的最小值。
发现其实等价于子树中结点到 \(1\) 号结点的最短路,原因考虑答案路径上最后一个非积水边的终点在所求子树内。
求子树的根结点可以考虑树上倍增。
单次时间复杂度 \(O(n \log n)\).
代码
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
#define il inline
const int maxn = 2e5 + 5;
const int maxm = 4e5 + 5;
const int maxt = maxn << 1;
const int lg_sz = 21;
struct edg
{
int u, v, l, a;
bool operator < (const edg& rhs) const { return (a > rhs.a); }
} edge[maxm];
int T, n, m, q, k, s;
bool vis[maxn];
int dis[maxn];
vector<int> g[maxn], w[maxn];
namespace DSU
{
int fa[maxt];
void dsu_init(int n) { for (int i = 1; i <= n; i++) fa[i] = i; }
int get(int x) { return (fa[x] == x ? x : (fa[x] = get(fa[x]))); }
}
using namespace DSU;
namespace tree
{
int nd = 0;
int anc[maxt][lg_sz], val[maxt];
int dp[maxt];
vector<int> tr[maxt];
void dfs(int u)
{
dp[u] = (u <= n ? dis[u] : 1061109567);
for (int v : tr[u]) dfs(v), dp[u] = min(dp[u], dp[v]), anc[v][0] = u;
}
void calc()
{
dfs(nd);
for (int j = 1; (1 << j) <= nd; j++)
for (int i = 1; i <= nd; i++)
anc[i][j] = anc[anc[i][j - 1]][j - 1];
}
int query(int u, int lim)
{
for (int i = lg_sz - 1; i >= 0; i--)
if (anc[u][i] && (val[anc[u][i]] > lim)) u = anc[u][i];
return dp[u];
}
}
using namespace tree;
void init()
{
for (int i = 1; i <= nd; i++) tr[i].clear();
for (int i = 1; i <= n; i++) g[i].clear(), w[i].clear();
}
void dijkstra()
{
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;
memset(vis, false, (n + 1) * sizeof(bool));
memset(dis, 0x3f, (n + 1) * sizeof(int)); dis[1] = 0;
pq.push(make_pair(0, 1));
while (!pq.empty())
{
int u = pq.top().second; pq.pop();
if (vis[u]) continue;
vis[u] = true;
for (int i = 0, v, len; i < g[u].size(); i++)
{
v = g[u][i], len = w[u][i];
if (dis[v] > dis[u] + len)
{
dis[v] = dis[u] + len;
pq.push(make_pair(dis[v], v));
}
}
}
}
void kruskal()
{
sort(edge + 1, edge + m + 1);
dsu_init(n), nd = n;
for (int i = 1; i <= m; i++)
{
int fu = get(edge[i].u), fv = get(edge[i].v);
if (fu != fv)
{
nd++, fa[nd] = fa[fu] = fa[fv] = nd, val[nd] = edge[i].a;
tr[nd].push_back(fu), tr[nd].push_back(fv);
}
}
}
void solve()
{
scanf("%d%d", &n, &m);
init();
for (int i = 1, u, v, l, a; i <= m; i++)
{
scanf("%d%d%d%d", &u, &v, &l, &a);
edge[i] = (edg){u, v, l, a};
g[u].push_back(v), w[u].push_back(l);
g[v].push_back(u), w[v].push_back(l);
}
dijkstra(), kruskal(), calc();
scanf("%d%d%d", &q, &k, &s);
int last_ans = 0;
while (q--)
{
int v, p;
scanf("%d%d", &v, &p);
v = (v + k * last_ans - 1) % n + 1;
p = (p + k * last_ans) % (s + 1);
printf("%d\n", last_ans = query(v, p));
}
}
int main()
{
scanf("%d", &T);
while (T--) solve();
return 0;
}