[Gym101194G] Pandaria 题解
[Gym101194G] Pandaria 题解
题目描述
给定一张无向图,边有边权,点有颜色 \(\le 10^6\),每次询问给定 \(x, w\),表示 Mr. Panda 从 \(x\) 出发,可以选定一个颜色 \(c\),使得在不走 \(> w\) 的边的情况下,能到达颜色为 \(c\) 的点的数量最大,输出这个 \(c\)。
强制在线。
\(n \le 10^5, m\le 2\times 10^5\)。
思路
看到强制在线和最大瓶颈路不难想到 Kruskal 重构树,在重构树上考虑问题,设点权不超过 \(w\) 且点权尽可能大的点为 \(u\),询问 \(u\) 子树内颜色出现次数的最大的颜色,
这个子树统计问题很明显可以用 DSU on Tree(树上启发式合并)或线段树合并做。
为了锻炼码力我写了线段树合并。
具体而言,对于每一个实点开一个动态开点的权值线段树,节点维护区间出现次数最多的颜色个数和颜色编号,可以 \(pushup\),然后在建立虚点的时候合并左右两个儿子的线段树即可。
时间复杂度:\(O(n\log n)\),\(n, m\) 视为同级。
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
// #define int long long
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 2e5 + 10, M = 8e6 + 10;
int n, m, c[N];
struct qwq {
int a, b, c;
bool operator < (const qwq &W) const {
return c < W.c;
}
} e[N];
int root[N], ls[M], rs[M], idx, maxc;
PII dat[M];
PII max(PII a, PII b) {
if(a.x == b.x) return (a.y < b.y ? a : b);
return (a.x > b.x ? a : b);
}
int update(int u, int l, int r, int x) {
if(!u) u = ++ idx;
if(l == r) return dat[u].x ++, dat[u].y = l, u;
int mid = l + r >> 1;
if(x <= mid) ls[u] = update(ls[u], l, mid, x);
else rs[u] = update(rs[u], mid + 1, r, x);
dat[u] = max(dat[ls[u]], dat[rs[u]]);
return u;
}
int merge(int a, int b, int l, int r) {
if(!a || !b) return a + b;
if(l == r) return dat[a].x += dat[b].x, a;
int mid = l + r >> 1;
ls[a] = merge(ls[a], ls[b], l, mid);
rs[a] = merge(rs[a], rs[b], mid + 1, r);
dat[a] = max(dat[ls[a]], dat[rs[a]]);
return a;
}
int fa[N], tot, f[N][20], cw[N];
PII p[N];
vector<int> g[N];
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
void Kruskal() {
sort(e + 1, e + m + 1);
for(int i = 1; i <= 2 * n; i ++)
g[i].clear();
tot = n;
for(int i = 1, a, b; i <= m; i ++) {
a = e[i].a, b = e[i].b;
int x = find(a), y = find(b);
if(x == y) continue;
g[++ tot].push_back(x), g[tot].push_back(y);
root[tot] = merge(root[x], root[y], 1, maxc);
p[tot] = dat[root[tot]];
cw[tot] = e[i].c;
fa[x] = fa[y] = fa[tot] = tot;
}
}
void dfs(int u, int fat) {
f[u][0] = fat;
for(int i = 1; i <= 19; i ++)
f[u][i] = f[f[u][i - 1]][i - 1];
for(auto v : g[u])
dfs(v, u);
}
int jump(int u, int w) {
for(int i = 19; i >= 0; i --)
if(f[u][i] && cw[f[u][i]] <= w)
u = f[u][i];
return u;
}
void work(int T) {
cin >> n >> m;
maxc = 0;
for(int i = 1; i <= n; i ++) cin >> c[i], fa[i] = i, maxc = max(maxc, c[i]);
memset(ls, 0, sizeof ls), memset(rs, 0, sizeof rs), idx = 0, memset(root, 0, sizeof root), memset(dat, 0, sizeof dat);
for(int i = 1; i <= n; i ++) root[i] = update(root[i], 1, maxc, c[i]), p[i] = dat[root[i]];
for(int i = 1, a, b, c; i <= m; i ++) {
cin >> a >> b >> c;
e[i] = {a, b, c};
}
Kruskal(), dfs(tot, 0);
int q, lst = 0;
cin >> q;
cout << "Case #" << T << ":\n";
while(q --) {
int x, w;
cin >> x >> w;
x ^= lst, w ^= lst;
cout << (lst = p[jump(x, w)].y) << '\n';
}
return ;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
for(int i = 1; i <= T; i ++) work(i);
return 0;
}
突发奇想:注意到重构树上的实点编号是连续的,可不可以利用 dfn 转化为区间最大值查询问题呢?Sparse Table 明显比线段树合并好写。
有机会写一下吧。