# 【圆方树】 $\text{Sol. Luogu P4606}$ 战略游戏
作者:互联网
\(\large \text{Date: 7.6}\)
\(\text{Sol. Luogu P4606}\) 战略游戏
—— 【圆方树】解法
题目描述
给出一个简单无向连通图。有 \(q\) 次询问:
每次给出一个点集 \(S(2\le |S|\le n)\),问有多少个点 \(u\) 满足 \(u \not\in S\),
且删掉 \(u\) 之后 \(S\) 中的点不全在一个连通分量中。
每个测试点有多组数据。
\(T\le 10^5,\sum |S|\le 2\times10^5,2\le n<m\le10^5\)
时限:\(10s\)
诶,这种题一看题解就知道跟双连通分量有关系。
因为题目只让你删一个点,所以要是两个 \(S\) 中的点处在同一个点双联通子图中,内部的点一定有另一条路径连通,外部的点完全可以找到其他路径实现连通,你是怎么删都没用的。所以,能删的点一定是割点。
想到这里,我们可以发现这题是一定要建圆方树的,因为树有许多图没有的算法,处理更方便。
于是套路性地建树——
然后怎么处理呢?我们发现,我们要求的就是 \(S\) 在圆方树上对应的连通子图中的圆点个数减去 \(|S|\),只有删了这些点(割点),才能将他们割裂开。
这里一定要无情地将构出来的圆方树当成一棵树!
怎么求这个使\(S\)中这些点连通的(唯一)点集的大小呢?《算法竞赛进阶指南》的第 \(384\) 页告诉我们,将这些点(\(S\))按照一定顺序(这里就按dfn[x]
排序)排好为 \(s_1,s_2,s_3,...,s_n\) ,依次覆盖 \(s_1\to s_2,s_2\to s_3,s_3\to s_4,...s_{n-1}\to s_n,\) \(\bold{s_n\to s_1}\) (都是两个端点)的树上路径所经过的点,此时这些点的连通子图的每一个点刚好被覆盖两边。
于是,我们让圆方树中方点的权值为 \(0\),圆点的权值为 \(1\),那“ \(S\) 在圆方树上对应的连通子图中的圆点个数减去 \(|S|\)”就变成了这个连通子图所含点的权值和。而这个权值和已经被我们转换成了 \(s_1,s_2,...s_n\) 这些点相邻两点(\(s_n\) 对 \(s_1\))的路径长(即所经过点的权值和)的和的 \(\dfrac12\)!那我们就只用实现任意两点之间的距离求解即可,倍增 \(LCA\) 随便搞。
然而题解说“如果子图中的深度最浅的节点是圆点,答案还要加上 \(1\),因为我们没有统计到它”,这个也太细了吧...
要加一句 if(QxLca(a[1], a[S]) <= n) ans += 2;
(这里 \(+2\) 是因为后面答案在输出时要 \(\div2\) )
\(\text{Code:}\)
#include <bits/stdc++.h>
#define rg register // 加 register 能快不少
using namespace std;
const int N = 3e5 + 5;
inline int read() {
int x = 0, w = 0; char ch = getchar();
while(!isdigit(ch)) { w |= ch == 45; ch = getchar(); }
while(isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
return w ? -x : x;
}
int Task, n, m, Ti, cnt, Q;
int low[N << 1], dfn[N << 1]; // 记得开 2 倍空间
int st[N], topx;
vector <int> Link[N], Lx[N];
void tarjan(int u) { // 建圆方树
low[u] = dfn[u] = ++Ti;
st[++topx] = u;
for(auto v : Link[u]) {
if(!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
if(low[v] == dfn[u]) { // 发现点双联通子图
++cnt;
for(rg int x = 0; x != v; --topx) {
x = st[topx];
Lx[cnt].push_back(x); // 建树
Lx[x].push_back(cnt);
}
Lx[cnt].push_back(u);
Lx[u].push_back(cnt);
}
}
else low[u] = min(low[u], dfn[v]);
}
}
int dep[N << 1], fa[N << 1][20], dis[N << 1];
void dfs(int u, int Fa) { // 预处理,标记 dep, dfn, dis, fa[x][0]
dfn[u] = ++Ti;
dep[u] = dep[fa[u][0] = Fa] + 1;
dis[u] = dis[Fa] + (u <= n ? 1 : 0);
for(auto v : Lx[u]) if(v != Fa) dfs(v, u);
}
inline void initLCA() { // 倍增递推
for(rg int j = 1; j <= 18; j++) {
for(rg int i = 1; i <= cnt; i++) {
fa[i][j] = fa[fa[i][j - 1]][j - 1];
}
}
}
inline int QxLca(int x, int y) { // 查询 LCA
if(dep[x] < dep[y]) swap(x, y);
for(rg int i = 18; i >= 0; i--) if(dep[fa[x][i]] >= dep[y]) x = fa[x][i];
if(x == y) return x;
for(rg int i = 18; i >= 0; i--) if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
inline void Solve() {
n = read(); m = read();
for(rg int i = 1; i <= n; i++) Link[i].clear();
for(rg int i = 1; i <= 2 * n; i++) Lx[i].clear();
for(rg int i = 1; i <= n; i++) dfn[i] = low[i] = 0;
for(rg int i = 1; i <= m; i++) {
int x = read(), y = read();
Link[x].push_back(y);
Link[y].push_back(x);
}
cnt = n;
Ti = 0; tarjan(1); topx--;
Ti = 0; dfs(1, 0);
initLCA();
Q = read();
while(Q--) {
rg int S, a[N];
S = read();
int ans = -2 * S;
for(rg int i = 1; i <= S; i++) a[i] = read();
sort(a + 1, a + S + 1, [](int i, int j) { // 按 dfn 排序
return dfn[i] < dfn[j];
});
for(rg int i = 1; i <= S; i++) {
int u = a[i], v = a[i % S + 1];
ans += dis[u] + dis[v] - 2 * dis[QxLca(u, v)]; // 两点距离
}
if(QxLca(a[1], a[S]) <= n) ans += 2; // 这个东西非常细!
printf("%d\n", (ans >> 1)); // 除以 2 输出
}
}
signed main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("Ans.txt", "w", stdout);
#endif
Task = read();
while(Task--) Solve();
return 0;
}
标签:连通,le,int,P4606,子图,Sol,fa,low,Luogu 来源: https://www.cnblogs.com/Doge297778/p/16452091.html