这 道 题 画 个 图 , 发 现 和 圆 方 树 关 系 很 大 ! ! 这道题画个图,发现和圆方树关系很大!! 这道题画个图,发现和圆方树关系很大!!
比 如 u 和 v 在 圆 方 树 上 的 路 径 经 过 几 个 圆 点 , 那 些 圆 点 就 符 合 要 求 ! ! 比如u和v在圆方树上的路径经过几个圆点,那些圆点就符合要求!! 比如u和v在圆方树上的路径经过几个圆点,那些圆点就符合要求!!
解 释 : 经 过 的 圆 点 连 接 着 两 个 方 点 , 分 别 属 于 u 的 点 双 和 v 的 点 双 解释:经过的圆点连接着两个方点,分别属于u的点双和v的点双 解释:经过的圆点连接着两个方点,分别属于u的点双和v的点双
去 掉 这 个 点 , u 和 v 分 割 开 来 去掉这个点,u和v分割开来 去掉这个点,u和v分割开来
所 以 现 在 就 是 求 给 定 的 s 集 合 任 意 两 点 路 径 经 过 的 圆 点 个 数 ( 不 重 复 ) 所以现在就是求给定的s集合任意两点路径经过的圆点个数(不重复) 所以现在就是求给定的s集合任意两点路径经过的圆点个数(不重复)
但是会超时
我 们 发 现 要 求 的 就 是 s 集 合 的 最 小 连 通 子 图 中 的 圆 点 啊 ! ! 我们发现要求的就是s集合的最小连通子图中的圆点啊!! 我们发现要求的就是s集合的最小连通子图中的圆点啊!!
那 怎 么 求 ? 那怎么求? 那怎么求?
想 象 一 下 d f s 的 过 程 , 搜 索 某 棵 子 树 , 然 后 原 路 返 回 , 在 搜 索 另 一 颗 子 树 . . . 想象一下dfs的过程,搜索某棵子树,然后原路返回,在搜索另一颗子树... 想象一下dfs的过程,搜索某棵子树,然后原路返回,在搜索另一颗子树...
也 就 是 按 照 d f s 序 给 s 集 合 排 序 也就是按照dfs序给s集合排序 也就是按照dfs序给s集合排序
我 们 只 需 要 计 算 相 邻 两 点 的 路 径 上 的 圆 点 即 可 ! ! ( 当 然 这 样 每 条 边 相 当 于 走 了 两 次 ) 我们只需要计算相邻两点的路径上的圆点即可!!(当然这样每条边相当于走了两次) 我们只需要计算相邻两点的路径上的圆点即可!!(当然这样每条边相当于走了两次)
最 后 除 以 2 减 去 s 中 集 合 的 个 数 即 可 最后除以2减去s中集合的个数即可 最后除以2减去s中集合的个数即可
s 集 合 首 位 元 素 也 要 计 算 路 径 上 的 贡 献 , 但 是 贡 献 是 点 , 不 好 统 计 s集合首位元素也要计算路径上的贡献,但是贡献是点,不好统计 s集合首位元素也要计算路径上的贡献,但是贡献是点,不好统计
我 们 把 每 个 圆 点 的 贡 献 放 在 它 和 父 亲 的 边 上 去 ( 贡 献 为 1 ) 我们把每个圆点的贡献放在它和父亲的边上去(贡献为1) 我们把每个圆点的贡献放在它和父亲的边上去(贡献为1)
这 样 我 们 可 以 很 方 便 的 用 l c a 来 求 树 上 两 点 的 距 离 ! ! 这样我们可以很方便的用lca来求树上两点的距离!! 这样我们可以很方便的用lca来求树上两点的距离!!
最 后 , 还 有 一 个 细 节 , 如 果 s 集 合 首 尾 元 素 的 l c a 是 圆 点 最后,还有一个细节,如果s集合首尾元素的lca是圆点 最后,还有一个细节,如果s集合首尾元素的lca是圆点
那 么 这 个 点 的 贡 献 没 有 算 进 去 ( 不 在 连 通 子 图 里 , 在 外 边 的 边 了 ) 那么这个点的贡献没有算进去(不在连通子图里,在外边的边了) 那么这个点的贡献没有算进去(不在连通子图里,在外边的边了)
需 要 额 外 加 上 去 需要额外加上去 需要额外加上去
#include <bits/stdc++.h> using namespace std; const int maxn=2e6+10; int t,n,m,f,q; vector<int>vec[maxn]; struct edge{ int to,nxt; }d[maxn]; int head[maxn],cnt=1; void add(int u,int v){ d[++cnt]=(edge){v,head[u]},head[u]=cnt; } void ins(int u,int v){ vec[u].push_back(v); } int low[maxn],dfn[maxn],stac[maxn],iid,top; void tarjan(int u) { low[u]=dfn[u]=++iid,stac[++top]=u; for(int i=head[u];i;i=d[i].nxt ) { int v=d[i].to; if( !dfn[v] ) { tarjan(v),low[u]=min(low[u],low[v]); if( low[v]>=dfn[u] ) { ++f;//发现割点 while( 1 ) { ins(f,stac[top]); ins(stac[top],f); if( stac[top--]==v ) break; } ins(f,u); ins(u,f); } } else low[u]=min( low[u],dfn[v] ); } } int id[maxn],fa[maxn][22],dis[maxn],num,deep[maxn],s[maxn]; void dfs(int u,int father) { id[u]=++num,deep[u]=deep[father]+1; fa[u][0]=father, dis[u]=dis[father]+( u<=n ); for(int i=1;i<=20;i++) fa[u][i]=fa[ fa[u][i-1] ][i-1]; for(int i=0;i<vec[u].size();i++) if( vec[u][i]!=father ) dfs( vec[u][i],u ); } int lca(int x,int y) { if( deep[x]<deep[y] ) swap(x,y); // for(int i=log(deep[x]);i>=0;i--) // if( deep[fa[x][i]]>=deep[y] ) x=fa[x][i]; for(int j=0,d=deep[x]-deep[y];d;j++,d>>=1) if( d&1 ) x=fa[x][j]; if( x==y ) return x; for(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]; } bool com(int a,int b){ return id[a]<id[b]; } void init() { iid=0,num=0,cnt=1,top=0; for(int i=1;i<=f;i++) { vec[i].clear(),head[i]=0; dfn[i]=low[i]=0; id[i]=dis[i]=deep[i]=0; for(int j=0;j<=20;j++) fa[i][j]=0; } } int main() { ios::sync_with_stdio(false); // cin.tie(0),cout.tie(0); cin >> t; while( t-- ) { cin >> n >> m; f=n; for(int i=1;i<=m;i++) { int l,r; cin >> l >> r; add(l,r); add(r,l); } tarjan(1),top--; dfs(1,0); cin >> q; while( q-- ) { int shu,ans=0; cin >> shu; ans=-2*shu; for(int i=1;i<=shu;i++) cin >> s[i]; sort(s+1,s+1+shu,com); for(int i=1;i<shu;i++) { int u=s[i],v=s[i+1]; ans+=dis[u]+dis[v]-2*dis[lca(u,v)]; } ans+=dis[s[1]]+dis[s[shu]]-2*dis[lca(s[1],s[shu])]; if( lca(s[1],s[shu])<=n ) ans+=2; cout << ans/2 << '\n'; } init(); } }