【洛谷P4606】战略游戏
题目
题目链接:https://www.luogu.com.cn/problem/P4606
省选临近,放飞自我的小 \(Q\) 无心刷题,于是怂恿小 \(C\) 和他一起颓废,玩起了一款战略游戏。
这款战略游戏的地图由 \(n\) 个城市以及 \(m\) 条连接这些城市的双向道路构成,并且从任意一个城市出发总能沿着道路走到任意其他城市。
现在小 \(C\) 已经占领了其中至少两个城市,小 \(Q\) 可以摧毁一个小 \(C\) 没占领的城市,同时摧毁所有连接这个城市的道路。只要在摧毁这个城市之后能够找到某两个小 \(C\) 占领的城市 \(u\) 和 \(v\),使得从 \(u\) 出发沿着道路无论如何都不能走到 \(v\),那么小 \(Q\) 就能赢下这一局游戏。
小 \(Q\) 和小 \(C\) 一共进行了 \(q\) 局游戏,每一局游戏会给出小 \(C\) 占领的城市集合 \(S\),你需要帮小 \(Q\) 数出有多少个城市在他摧毁之后能够让他赢下这一局游戏。
\(1 ≤ T ≤ 10\),\(2 ≤ n ≤ 10^5\) 且 \(n-1≤m≤2×10^5\),\(1 ≤ q ≤ 10^5\),对于每组测试数据,有 \(∑|S| ≤ 2 × 10^5\)。
思路
等价于询问任意选择两个集合内的点,其路径上必经点的并集大小。
Tarjan 把点双缩一下,建出圆方树,然后每一次询问将点按照圆方树上点的 dfn 排序。
枚举 dfn 序相邻两个点,他们之间的贡献为路径上圆点的数量。所以我们把圆点的权值设为 \(1\),方点为 \(0\),然后求出 LCA 就可以知道路径上权值之和了。注意需要减去 \(u,v\) 两个点的贡献 \(2\)。
但是这样计算容易算重且不好容斥,所以我们把每一个点的权值放到他和他父节点之间的道路上,这样每一条路会被计算两次。最终答案除以 \(2\) 即可。但是我们这样会漏掉 dfn 序最小和最大的点 LCA 的贡献。所以再加回来即可。
时间复杂度 \(O(\sum n\log n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=400010,LG=20;
int Q1,Q,n,m,t,ans,a[N];
struct edge
{
int next,to;
};
struct Tree
{
int tot,head[N],dfn[N],cnt[N],dep[N],f[N][LG+1];
edge e[N];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
void dfs(int x,int fa)
{
dfn[x]=++tot; dep[x]=dep[fa]+1;
cnt[x]=cnt[fa]+(x<=n);
f[x][0]=fa;
for (int i=1;i<=LG;i++)
f[x][i]=f[f[x][i-1]][i-1];
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa) dfs(v,x);
}
}
int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
for (int i=LG;i>=0;i--)
if (dep[f[x][i]]>=dep[y]) x=f[x][i];
if (x==y) return x;
for (int i=LG;i>=0;i--)
if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
}T;
bool cmp(int x,int y)
{
return T.dfn[x]<T.dfn[y];
}
struct Graph
{
int tot,cnt,head[N],dfn[N],low[N];
edge e[N];
stack<int> st;
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
void tarjan(int x)
{
dfn[x]=low[x]=++tot;
st.push(x);
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (!dfn[v])
{
tarjan(v);
low[x]=min(low[x],low[v]);
if (low[v]>=dfn[x])
{
int y; cnt++;
do {
y=st.top(); st.pop();
T.add(cnt,y); T.add(y,cnt);
}while (y!=v);
T.add(x,cnt); T.add(cnt,x);
}
}
else low[x]=min(low[x],dfn[v]);
}
}
}G;
void prework()
{
while (G.st.size()) G.st.pop();
memset(G.head,-1,sizeof(G.head));
memset(G.dfn,0,sizeof(G.dfn));
memset(T.head,-1,sizeof(T.head));
G.tot=T.tot=0;
}
int main()
{
scanf("%d",&Q1);
while (Q1--)
{
prework();
scanf("%d%d",&n,&m);
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
G.add(x,y); G.add(y,x);
}
G.tot=0; G.cnt=n;
G.tarjan(1);
T.tot=0;
T.dfs(1,0);
scanf("%d",&Q);
while (Q--)
{
scanf("%d",&t);
for (int i=1;i<=t;i++)
scanf("%d",&a[i]);
sort(a+1,a+1+t,cmp);
a[++t]=a[1]; ans=0;
for (int i=2;i<=t;i++)
{
int p=T.lca(a[i],a[i-1]);
ans+=T.cnt[a[i]]+T.cnt[a[i-1]]-2*T.cnt[p]-2;
}
printf("%d\n",ans/2+(T.lca(a[1],a[t-1])<=n));
}
}
return 0;
}