战略游戏(杏彩平台出租圆方树+虚树)

Solution
首先介绍杏彩平台出租 QQ2952777280【话仙源码论坛】hxforum.com【木瓜源码论坛】papayabbs.com 一下圆方树。
先求点双连通分量,设原图中的点为圆点。
然后对于每个点双连通分量建一个方点,从方点向连通分量内的所有点连一条边,并把这个连通分量里原有的边删掉。
在 Tarjan 求点双的过程中,所有点分别入栈了一次,而除了根之外所有的点都出栈了一次,设有 mm 个点双,那么除了出栈过的 n−1n−1 个点进入点双之外,每次找到连通分量时还要把一个割点算入,这样所有点双的点数之和为 n+m−1n+m−1 ,而上面的构图中圆点和方点的总数为 n+mn+m ,所以得出,这样得到的是一棵树,这棵树就是圆方树。
圆方树有一些神奇的性质:
(1)原图的割点对应圆方树的割点(方点除外),也就是属于多个方点的圆点。
(2)两个点 u,vu,v 之间的割点集合为圆方树 uu 到 vv 的路径上除 uu 和 vv 的所有圆点。
回到问题。建出圆方树,可以把问题进行转化。
求一个点集之间两两的路径的并上,有多少个圆点(这个选出的点集除外)。
点集之间两两的路径并实际上就是这个点集构成的虚树。
把关键点按 DFS 序排序后我们要求的就是求相邻两个点之间路径的并。
我们给每条边一个权值: (u,v)(u,v) 边,如果深度较大的点 vv 为圆点则 (u,v)(u,v) 的权值为 11 ,否则为 00 。
这样,问题再次转化:
求点集 SS 所在虚树的边权和,减去 |S||S| ,如果虚树的根为圆点则加一。
容易想到,点集 SS 的虚树的边权和,
就是 DFS 序排序之后 11 到 22 , 22 到 33 ,…, |S|−1|S|−1 到 SS , SS 到 11 的路径边权之和除以 22 。
利用倍增 LCA 即可求得。

Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
#define Tree(u) for (int e = adj2[u], v; e; e = nxt2[e]) if ((v = go2[e]) != fu)
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, M = N << 1, L = M << 1, LogN = 20;
int n, m, q, ecnt, nxt[L], adj[N], go[L], T, dfn[N], low[N], top,
stk[N], nm, ecnt2, nxt2[L], adj2[M], go2[L], dep[M], fa[M][LogN],
vn, vir[N], pos[M], QAQ, dis[M];
void add_edge(int u, int v) {
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
void add_edge2(int u, int v) {
nxt2[++ecnt2] = adj2[u]; adj2[u] = ecnt2; go2[ecnt2] = v;
nxt2[++ecnt2] = adj2[v]; adj2[v] = ecnt2; go2[ecnt2] = u;
}
void dfs(int u) {
dfn[stk[++top] = u] = low[u] = ++T;
Edge(u)
if (!dfn[v]) {
dfs(v);
low[u] = min(low[u], low[v]);
if (dfn[u] <= low[v]) {
nm++;
while (stk[top] != v) add_edge2(nm, stk[top--]);
add_edge2(nm, v); top--;
add_edge2(nm, u);
}
}
else low[u] = min(low[u], dfn[v]);
}
void dfsLCA(int u, int fu) {
int i;
pos[u] = ++QAQ;
dis[u] = u == 1 ? 0 : dis[fu] + (u <= n);
dep[u] = dep[fa[u][0] = fu] + 1;
For (i, 0, 17) fa[u][i + 1] = fa[fa[u][i]][i];
Tree(u) dfsLCA(v, u);
}
int lca(int u, int v) {
int i;
if (dep[u] < dep[v]) swap(u, v);
Rof (i, 18, 0) {
if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
if (u == v) return u;
}
Rof (i, 18, 0) if (fa[u][i] != fa[v][i])
u = fa[u][i], v = fa[v][i];
return fa[u][0];
}
bool comp(int a, int b) {
return pos[a] < pos[b];
}
int solve() {
int i, sum = 0, rot = vir[1];
sort(vir + 1, vir + vn + 1, comp);
For (i, 1, vn) {
int u = vir[i], v = vir[i == vn ? 1 : i + 1], w;
w = lca(u, v);
if (dep[w] < dep[rot]) rot = w;
sum += dis[u] + dis[v] - (dis[w] << 1);
}
return (sum >> 1) + (rot <= n) - vn;
}
void work() {
ecnt = T = top = ecnt2 = QAQ = 0;
int i, x, y;
n = nm = read(); m = read();
For (i, 1, n) adj[i] = dfn[i] = 0;
For (i, 1, (n << 1)) adj2[i] = 0;
For (i, 1, m) x = read(), y = read(), add_edge(x, y);
dfs(1); dfsLCA(1, 0);
q = read();
while (q--) {
vn = read();
For (i, 1, vn) vir[i] = read();
printf("%d\n", solve());
}
}
int main() {
int T = read();
while (T--) work();
return 0;
}

猜你喜欢

转载自blog.51cto.com/13912520/2155713