【题解】CF487E Tourists / 圆方树
概念
圆方树是一种基于无向图构造的树。
我们知道,圆方树最早是 WC 上提出的处理仙人掌的东西,用于将树上做法拓展到复杂度正确的仙人掌做法。
但是一些关于点双有性质的题也可以用圆方树转化成树上问题,例如这个。
构造
对于原图中的点,称之为圆点。
对于原图的每个点双,考虑为其虚拟一个对应的结点,称之为方点。
从方点向原图中所有的圆点连边,得到的是一个森林。原图中每个点双和森林中的一棵树对应。
特别地,当原图连通时,得到的是一棵树,称为圆方树。
性质
-
圆点、方点均不会和同类型的点相邻。
-
圆方树的点数是 \(O(n)\) 的。
题解
对于此题,注意到到达某个点双时一定有路径经过其中的任意一点,所以这个点双对答案的贡献等价于其中点权最小的结点对答案的贡献。
因此考虑对原图建圆方树。
于是询问直接上树剖做就行。
考虑到修改的时候会影响到包含该结点的相邻方点,于是比较难修改。
考虑只钦定方点的答案为子树中圆点的最小贡献,询问时如果 \(lca\) 是方点就加上缺少的贡献。
时间复杂度 \(O(n \log n)\).
#include <cstdio>
#include <iostream>
#include <vector>
#include <set>
using namespace std;
#define il inline
#define pb emplace_back
const int maxn = 2e5 + 5;
const int maxm = 2e5 + 5;
const int nd_sz = maxn << 1;
const int sgt_sz = nd_sz << 2;
const int inf = 2147483647;
int n, m, q;
int seq[maxn];
multiset<int> val[nd_sz];
il int read()
{
int res = 0;
char ch = getchar();
while ((ch < '0') || (ch > '9')) ch = getchar();
while ((ch >= '0') && (ch <= '9')) res = res * 10 + ch - '0', ch = getchar();
return res;
}
il void write(int x)
{
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
il void write(int x, char ch) { write(x), putchar(ch); }
namespace tree
{
int nd, cnt_pos;
int fa[nd_sz], son[nd_sz], top[nd_sz];
int pos[nd_sz], rk[nd_sz], dep[nd_sz], sz[nd_sz];
vector<int> g[nd_sz];
il void dfs1(int u, int f)
{
fa[u] = f, dep[u] = dep[f] + 1, sz[u] = 1;
for (int v : g[u])
if (v != f)
{
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
}
il void dfs2(int u, int t)
{
// printf("dfs2 %d %d %d %d\n", u, son[u], fa[u], t);
rk[pos[u] = ++cnt_pos] = u, top[u] = t;
if (son[u]) dfs2(son[u], t);
for (int v : g[u])
if ((v != son[u]) && (v != fa[u])) dfs2(v, v);
}
il int lca(int u, int v)
{
while(top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]]) swap(u, v);
u = fa[top[u]];
}
return (dep[u] < dep[v] ? u : v);
}
}
namespace SGT
{
#define ls (k << 1)
#define rs (k << 1 | 1)
#define top tree::top
#define dep tree::dep
#define nd tree::nd
#define pos tree::pos
int val[sgt_sz];
il void push_up(int k) { val[k] = min(val[ls], val[rs]); }
il void build(int k, int l, int r)
{
if (l == r) return val[k] = seq[tree::rk[l]], void();
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
push_up(k);
}
il void update(int k, int l, int r, int p, int w)
{
if (l == r) return val[k] = w, void();
int mid = (l + r) >> 1;
if (p <= mid) update(ls, l, mid, p, w);
else update(rs, mid + 1, r, p, w);
push_up(k);
}
il int query(int k, int l, int r, int ql, int qr)
{
if ((l >= ql) && (r <= qr)) return val[k];
int mid = (l + r) >> 1, res = inf;
if (ql <= mid) res = query(ls, l, mid, ql, qr);
if (qr > mid) res = min(res, query(rs, mid + 1, r, ql, qr));
return res;
}
il int qry_path(int u, int v)
{
int res = inf;
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]]) swap(u, v);
res = min(res, query(1, 1, nd, pos[top[u]], pos[u]));
u = tree::fa[top[u]];
}
if (dep[u] > dep[v]) swap(u, v);
res = min(res, query(1, 1, nd, pos[u], pos[v]));
if (u > n) res = min(res, seq[tree::fa[u]]);
return res;
}
#undef top
#undef nd
}
namespace graph
{
#define g tree::g
struct node
{
int fr, to, nxt;
} edge[maxm << 1];
int cnt_eg, cnt_ver, cnt_vdcc;
int top, stk[maxn];
int head[maxn], dfn[maxn], low[maxn], bel[maxn];
il void add_edge(int u, int v) { edge[++cnt_eg] = (node){u, v, head[u]}, head[u] = cnt_eg; }
il void tarjan(int u)
{
dfn[u] = low[u] = ++cnt_ver, stk[++top] = u;
for (int i = head[u], v, tp; i; i = edge[i].nxt)
{
v = edge[i].to;
if (!dfn[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u])
{
cnt_vdcc++;
// printf("debug %d\n", cnt_vdcc);
do
{
tp = stk[top--];
// printf("%d <-> %d\n", cnt_vdcc, tp);
bel[u] = cnt_vdcc, g[tp].pb(cnt_vdcc), g[cnt_vdcc].pb(tp);
} while (tp != v);
// printf("%d <-> %d\n", cnt_vdcc, u);
g[u].pb(cnt_vdcc), g[cnt_vdcc].pb(u);
}
}
else low[u] = min(low[u], dfn[v]);
}
}
}
int main()
{
graph::cnt_vdcc = n = read(), m = read(), q = read();
for (int i = 1; i <= n; i++) seq[i] = read();
for (int i = 1, u, v; i <= m; i++) u = read(), v = read(), graph::add_edge(u, v), graph::add_edge(v, u);
// graph::cnt_vdcc = n;
for (int i = 1; i <= n; i++)
if (!graph::dfn[i]) graph::tarjan(i);
tree::dfs1(1, 0);
tree::dfs2(1, 1);
// puts("done");
for (int i = 2; i <= n; i++) val[tree::fa[i] - n].insert(seq[i]);
for (int i = n + 1; i <= graph::cnt_vdcc; i++) seq[i] = ((!val[i - n].empty()) ? (*val[i - n].begin()) :inf);
tree::nd = graph::cnt_vdcc;
// for (int i = 1; i <= tree::nd; i++) printf("%d ", seq[i]); puts("");
SGT::build(1, 1, tree::nd);
while (q--)
{
int a, b, nw;
char ch = getchar();
while ((ch != 'A') && (ch != 'C')) ch = getchar();
// puts("");
// for (int i = 1; i <= tree::nd; i++) printf("%d ", seq[i]);
// puts("");
if (ch == 'C')
{
a = read(), nw = read();
SGT::update(1, 1, tree::nd, pos[a], nw);
if (a == 1) { seq[a] = nw; continue; }
int u = tree::fa[a];
// printf("debug u = %d\n", u);
val[u - n].erase(val[u - n].find(seq[a]));
val[u - n].insert(nw);
int mnv = *val[u - n].begin();
// printf("debug mnv = %d\n", mnv);
if (mnv == seq[u]) { seq[a] = nw; continue; }
SGT::update(1, 1, tree::nd, pos[u], mnv);
seq[u] = mnv, seq[a] = nw;
}
else
{
a = read(), b = read();
write(SGT::qry_path(a, b), '\n');
}
}
return 0;
}