洛谷题单指南-连通性问题-P4606 [SDOI2018] 战略游戏
原题链接:https://www.luogu.com.cn/problem/P4606
题意解读:连通的无向图,给出若干个询问,每个询问是一组点,求图中有多少个点删去会造成这组点不连通。
解题思路:
1、问题分析
对于询问中的一组点,可以构建其最小连通的子图(也就是树),就是要求这棵树中所有的点数再减去已知的点数。
对于图中一组点,构造的树,称为虚树(https://oi-wiki.org/graph/virtual-tree/)
比如:已知点是4、6、7
但本题与虚树有所不同,对于已知点4、6、7,最小连通的树涉及的节点包括:4、2、1、3、6、7
那如何要求得最小连通树的节点数呢?
2、算法流程
由于原图不易处理,可以先将无向图转化成圆方树;
点权不易处理,可以转成边权,圆点的父边权值为1,方点的父边权值为0;
将已知点按DFN排序,再两两求lca,累加每相邻两点到lca的距离之和,最后再累加首项和末项到lca的距离,除以2,就得到最小连通树除根节点外的节点个数,根节点就是首项和末项的lca,如果根节点是圆点,节点数再加1,方点则忽略。
如此即可算出一组点的最小连通树中所有节点的个数,减去已知点的个数即得到答案。
举例说明以上计算过程:
如图,已知点4,5,6,7
先将点按dfn排序,得到4,5,6,7
求4、5的lca,是2,4、5到2的距离之和为2,ans += 2
求5、6的lca,是1,5、6到1的距离之和为4,ans += 4
求6、7的lca,是3,6、7到1的距离之和为2,ans += 2
求4、7的lca,是1,4、7到1的距离之和为4,ans += 4
此时ans = 12,ans / 2 = 6,再看根节点1是圆点,因此ans = 7
最后,减去已知点数ans = 7 - 4 = 3,正好是1,2,3三个点的数量
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 200005, M = 800005;
int head1[N], head2[N], to[M], nxt[M], idx; //head1:原图 head2:圆方树
int dfn[N], low[N], timestamp;
int stk[N], top;
int cnt; //点双个数
int dist[N]; //圆方树中到根节点的距离
int depth[N], fa[N][18]; //圆方树中节点深度、第2^i个父节点信息
int a[N]; //一次询问中已占领的点
int t, n, m;
bool cmp(int x, int y)
{
return dfn[x] < dfn[y];
}
void add(int head[], int a, int b)
{
to[++idx] = b;
nxt[idx] = head[a];
head[a] = idx;
}
void tarjan(int u, int p)
{
dfn[u] = low[u] = ++timestamp;
stk[++top] = u;
for(int i = head1[u]; i; i = nxt[i])
{
int v = to[i];
if(v == p) continue;
if(!dfn[v])
{
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u])
{
cnt++;
add(head2, cnt + n, u), add(head2, u, cnt + n); //圆方树建边
while(true)
{
int t = stk[top--];
add(head2, cnt + n, t), add(head2, t, cnt + n); //圆方树建边
if(t == v) break;
}
}
}
else low[u] = min(low[u], dfn[v]);
}
}
void dfs(int u, int p)
{
dfn[u] = ++timestamp;
depth[u] = depth[p] + 1;
dist[u] = dist[p] + (u <= n); //当前节点是圆点距离+1,当前节点是方点距离+0
fa[u][0] = p;
for(int i = 1; i < 18; i++)
{
fa[u][i] = fa[fa[u][i - 1]][i - 1];
}
for(int i = head2[u]; i; i = nxt[i])
{
int v = to[i];
if(v == p) continue;
dfs(v, u);
}
}
int lca(int u, int v)
{
if(depth[u] < depth[v]) swap(u, v);
for(int i = 17; i >= 0; i--)
{
if(depth[fa[u][i]] >= depth[v])
{
u = fa[u][i];
}
}
if(u == v) return u;
for(int i = 17; i >= 0; i--)
{
if(fa[u][i] != fa[v][i])
{
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
void clear()
{
memset(head1, 0, sizeof(head1));
memset(head2, 0, sizeof(head2));
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
idx = 0, top = 0, cnt = 0, timestamp = 0;
}
int main()
{
cin >> t;
while(t--)
{
clear();
cin >> n >> m;
for(int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
add(head1, u, v), add(head1, v, u);
}
tarjan(1, 0); //建立圆方树
timestamp = 0;
dfs(1, 0); //dfs重新计算dfn,预处理lca相关数组,以及到根节点的距离
int q;
cin >> q;
while(q--)
{
int s;
cin >> s;
for(int i = 1; i <= s; i++) cin >> a[i];
sort(a + 1, a + s + 1, cmp); //已占领点按dfn排序
int ans = 0;
for(int i = 1; i < s; i++)
{
int u = a[i], v = a[i + 1], l = lca(u, v);
ans += dist[u] + dist[v] - 2 * dist[l];
}
int u = a[1], v = a[s], l = lca(u, v);
ans += dist[u] + dist[v] - 2 * dist[l];
ans = ans / 2 + (l <= n) - s;
cout << ans << endl;
}
}
return 0;
}