被CNST的大小卡了好久。一定要开到18呀……
首先,遇到这种带各种各样环的图先考虑是不是可以建立圆方树,在圆方树上求出答案。然后转化为圆方树之后,我们就将图转化到了树上。答案非常的明显:只要一个圆点位于一个节点到另一个节点的路径上,它就是一个可以选择的答案点。
又观察到数据范围中给出的总和 <= & 多组询问的模式,立马联想到建立虚树。建立出了虚树,我们发现这棵虚树有一个非常妙妙的性质:所有的叶子节点均为指定点。这样的话,在这棵虚树上所有的点(从叶子到根的路径上的点,包括没有建出来的点)均为合法的答案。不过要注意到因为我们自动建立出了1点为根节点,所以要防止1没有被选择,要减去这一段非法的点数。
// luogu-judger-enable-o2 #include <bits/stdc++.h> using namespace std; #define maxn 200000 #define CNST 19 int n, m, K, tot, T; int timer, dfn[maxn], low[maxn]; int dep[maxn], dis[maxn], gra[maxn][CNST]; int ans, S[maxn], a[maxn]; bool vis[maxn]; struct edge { int cnp = 1, head[maxn], to[maxn * 2], last[maxn * 2]; void add(int u, int v) { if(u == v) return; to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++; to[cnp] = u, last[cnp] = head[v], head[v] = cnp ++; } void Clear() { cnp = 1; memset(head, 0, sizeof(head)); } }E1, E2, E3; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } void Tarjan(int u) { dfn[u] = low[u] = ++ timer; S[++ S[0]] = u; for(int i = E1.head[u]; i; i = E1.last[i]) { int v = E1.to[i]; if(!dfn[v]) { Tarjan(v); low[u] = min(low[u], low[v]); if(low[v] >= dfn[u]) { E2.add(++ tot, u); int x = 0; do { x = S[S[0] --]; E2.add(tot, x); }while(x != v); } } else low[u] = min(low[u], dfn[v]); } } void dfs(int u, int fa) { dis[u] = 0, dep[u] = 0; gra[u][0] = fa, dfn[u] = ++ timer, dep[u] = dep[fa] + 1; if(u <= n) dis[u] += 1; dis[u] += dis[fa]; for(int i = 1; i < CNST; i ++) gra[u][i] = gra[gra[u][i - 1]][i - 1]; for(int i = E2.head[u]; i; i = E2.last[i]) { int v = E2.to[i]; if(v != fa) dfs(v, u); } } int LCA(int x, int y) { if(dep[x] < dep[y]) swap(x, y); for(int i = CNST - 1; ~i; i --) if(dep[gra[x][i]] >= dep[y]) x = gra[x][i]; for(int i = CNST - 1; ~i; i --) if(gra[x][i] != gra[y][i]) x = gra[x][i], y = gra[y][i]; return x == y ? x : gra[x][0]; } bool cmp(int a, int b) { return dfn[a] < dfn[b]; } void DP(int u, int fa) { for(int i = E3.head[u]; i; i = E3.last[i]) { int v = E3.to[i]; if(v == fa) continue; DP(v, u); ans += dis[v] - dis[u]; } E3.head[u] = 0; } void Work() { E3.cnp = 1; K = read(), tot = 1, S[1] = 1, S[0] = 1; for(int i = 1; i <= K; i ++) a[i] = read(); sort(a + 1, a + 1 + K, cmp); int L = a[1]; for(int i = 1; i <= K; i ++) { int lca = LCA(S[S[0]], a[i]); L = LCA(L, a[i]); while(23333) { if(dep[lca] >= dep[S[S[0] - 1]]) { E3.add(S[S[0]], lca); S[0] --; if(lca != S[S[0]]) S[++ S[0]] = lca; break; } if(S[0]) E3.add(S[S[0]], S[S[0] - 1]), S[0] --; } S[++ S[0]] = a[i]; } while(S[0] > 1) E3.add(S[S[0]], S[S[0] - 1]), S[0] --; ans = 0; DP(1, 0); if(L > n) printf("%d ", ans - dis[L] + 1 - K); else printf("%d ", ans - dis[L] + 2 - K); } void init() { E1.Clear(), E2.Clear(), timer = 0; memset(gra, 0, sizeof(gra)); } int main() { scanf("%d", &T); while(T --) { init(); tot = n = read(), m = read(); for(int i = 1; i <= n; i ++) dfn[i] = low[i] = 0; for(int i = 1; i <= m; i ++) { int u = read(), v = read(); E1.add(u, v); } S[0] = 0, Tarjan(1); int Q = read(); timer = 0; dfs(1, 0); for(int i = 1; i <= Q; i ++) Work(); } return 0; }