【洛谷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;
}
posted @ 2021-01-14 19:40  stoorz  阅读(104)  评论(0)    收藏  举报