对于这道题, 我们如何才能使得两个点变得不连通呢?
当然是干掉我们路径上必须经过的点,Which is called 割点
而这里也就要利用到我们圆方树的性质。 这些割点就是我们圆方树上的圆点。
于是,我们轻松的想到一个办法: 直接找出所有的圆点不就好了? 然鹅,我们的时间复杂度这样是过不去的。
那么,如何快速求出所有圆点呢? 不妨换一种思路。 对于一个圆方树,如果我们能找到其包含所有点的连通块(当然是最小的,类似于最小生成树的东西),然后将其减掉s,就是我们的圆点的数量。
(画图手摸)
如果我们将所有的点按照dfs序排序(即dfn),然后依次遍历(请自行画图),发现我们将会得到一个联通图。这不就是我们要找的吗!!
那么,如何统计其大小呢? 也很简单。令 val[方点] = 0, val[圆点] = 0,然后倍增求路径点权和即可。
友情tips:不要忘了,LCA会被多次经过,要记得减掉。1号点和s号点的LCA不会被统计到,记得特判其圆点方点,然后加上。
#include <bits/stdc++.h> using namespace std; #define N 1000010 #define isdigit(c) ((c)>='0'&&(c)<='9') #define I_WANT_TO_AK_IOI 0 int read(){ int x = 0, s = 1; char c = getchar(); while(!isdigit(c)){ if(c == '-')s = -1; c = getchar(); } while(isdigit(c)){ x = (x << 1) + (x << 3) + (c ^ '0'); c = getchar(); } return x * s; } struct tu{ /* “图 ” */ int v, next; }; int n, m; struct node{ tu t[N << 1]; int f[N << 1]; int bian; inline void add(int u, int v){ /*双向边*/ t[++bian] = (tu){v, f[u]}, f[u] = bian; t[++bian] = (tu){u, f[v]}, f[v] = bian; return ; } /* 调试错误 void out(){ this-> bian; printf("%d ", bian); return ; }*/ void clean(){ bian = 0; memset(f, 0, sizeof(f)); for(int i = 1;i <= (N << 1); i++) t[i] = (tu){0, 0}; return ; } }G, T; struct bcc{ int dfn[N << 1], low[N], stac[N], top; int bcccnt, id; void tarjan(int now){ /*求圆方树*/ dfn[now] = low[now] = ++id; stac[top++] = now; for(int i = G.f[now]; i; i = G.t[i].next){ int v = G.t[i].v; if(!dfn[v]){ tarjan(v); low[now] = min(low[now], low[v]); if(dfn[now] <= low[v]){ bcccnt++; do{ T.add(bcccnt, stac[--top]); } while(stac[top] != v); T.add(bcccnt, now); /*圆方树建图*/ } } else low[now] = min(low[now], dfn[v]); } return ; } inline void clean(){ id = 0; top = 0; bcccnt = 0; memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); return ; } } BCC; namespace LCA{ int size[N << 1], deth[N << 1], dfn[N << 1], fa[N << 1][22], id = 0; int val[N], siz[N]; void dfs(int now,int father){ size[now] = 1; deth[now] = deth[father] + 1; dfn[now] = ++id; fa[now][0] = father; val[now] = val[father] + (now <= n); for(int i = T.f[now]; i; i = T.t[i].next){ int v = T.t[i].v; if(v != father){ dfs(v, now); siz[now] += siz[v]; } } return ; } void prepare(){ /*倍增预处理*/ for(int j = 1;j <= 20; j++) for(int i = 1;i <= BCC.bcccnt; i++) fa[i][j] = fa[fa[i][j-1]][j-1]; return ; } int get_lca(int x, int y){ /*倍增求LCA*/ if(deth[x] < deth[y]) swap(x, y); for(int i = 20; ~i; i--){ if(deth[fa[x][i]] >= deth[y]) x = fa[x][i]; } if(x == y) return x; for(int i = 20; ~i; i--){ if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i]; } return fa[x][0]; } inline bool cmp(int x, int y){ return dfn[x] < dfn[y]; } inline void clean(){ id = 0; memset(dfn, 0, sizeof (dfn)); memset(fa, 0, sizeof(fa)); memset(val,0, sizeof (val)); memset(deth, 0, sizeof(deth)); memset(size, 0, sizeof(size)); return ; } } inline void clean(){ BCC.clean(); T.clean(); G.clean(); LCA::clean(); return ; } int a[N]; int main(){ // freopen("hh.txt", "r", stdin); int test = read(); while(test--){ n = read(), m = read(); for(int i = 1;i <= m; i++){ int x = read(), y = read(); G.add(x, y); /*先建原图*/ } BCC.bcccnt = n; BCC.tarjan(1); /*建立圆方树*/ LCA::dfs(1, 0); LCA::prepare(); /*处理倍增和路径点权和*/ int q = read(); while(q--){ int s = read(), ans = 0; for(int i = 1;i <= s; i++) a[i] = read(); sort(a + 1, a + s + 1, LCA::cmp); /*按照dfn排序*/ a[s + 1] = a[1]; for(int i = 1;i <= s; i++){ int lca = LCA::get_lca(a[i], a[i + 1]); ans += LCA::val[a[i]] + LCA::val[a[i + 1]] - (LCA::val[lca] << 1); } ans >>= 1; ans -= s; ans += (LCA::get_lca(a[1], a[s]) <= n); printf("%d ", ans); } clean(); /*不要忘了清空呀*/ } return I_WANT_TO_AK_IOI; }
首道黑题,也是首次尝试大量的代码分段