演唱会

演唱会

圆方树、点双连通分量、树上启发式合并、长链剖分

题意

给你一个 \(n\) 个点,\(m\) 条边的无向图,求从 \(u\) 出发到 \(v\) 必须经过的点恰好有 \(L\) 个的点对 \((u,v)\) 的个数。

其中 \(1\leq n \leq 2\times 10^6,1\leq m \leq 4 \times 10^6\)

解法

对于一组点对 \((u,v)\)

首先起点和终点一定必须经过。

\(x\to y\) 有多于 \(1\) 条路径, 则 \(x,y\) 之间所有点都是必须经过的。

因此我们将图建成基于点双圆方树,题目变为在树上找 \(u,v\) 都是圆点,路径长 \(l\times 2-1\) 的路径 \(u\to v\) 的个数。

可以 dsu on tree点分治解决,时间复杂度 \(O(n\log n)\)

然而这会超时 qwq,我们需要 \(O(n)\) 的算法。

考虑 dsu on tree,我们统计答案需要的信息是子树中从根出发的长度为 \(dis\) 的路径的个数,将它保存在 \(dp_{dis}\) 里。

对于节点 \(u\),它直接继承重儿子的答案,然后暴力枚举合并轻儿子。

留意到重儿子的 \(dis\) 最大不超过 \(size_{son}\),轻儿子 \(v\)\(dis\) 最大不超过 \(size_v\),因此我们完全可以记录下轻儿子的 \(dp\) 值,枚举轻儿子距离为 \(j(1\le j \le maxdis_v)\) 的路径,乘法原理统计答案。

为减小复杂度,我们要尽可能减少枚举轻儿子 \(j\) 的次数,因此我们把重儿子设为深度最大的儿子,即长儿子,用长剖代替重剖。可以证明枚举次数总共不超过 \(n\),因此算法时间复杂度为 \(O(n)\)

AC Code

#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define il inline
using namespace std;
typedef long long ll;
const int N=2e6+2,M=4e6+2;

int n,m,l;
int u,v,head[N];
int cnt;
struct edge { int to,ne; } e[M<<1] ;
void add (int u,int v) { e[++cnt]= {v,head[u]}, head[u]=cnt; }

int dfn[N],low[N];
int st[N],top;
vector<int> to[N<<1];
int tot;
void tarjan (int u) {
	dfn[u]=low[u]=++cnt;
	st[++top]=u;
	for(int i=head[u];i;i=e[i].ne) {
		int v=e[i].to;
		if(!dfn[v]) {
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(dfn[u]==low[v]) {
				tot++;
				to[u].push_back(tot);
				while(st[top]!=v){
					to[tot].push_back(st[top]);
					top--;
				}
				to[tot].push_back(st[top]);
				top--;
			}
		}else{
			low[u]=min(low[u],dfn[v]);
		}
	}
}

int dep[N<<1],gson[N<<1];
ll ans;
int dp[N<<1];
void find_g(int u){
	for(int v:to[u]){
		find_g(v);
		dep[u]=max(dep[u],dep[v]+1);
		if(dep[v]>=dep[gson[u]]) gson[u]=v;
	}
}
void dsu(int u){
	dfn[u]=++cnt;
	if(gson[u]) dsu(gson[u]);
	for(int v:to[u]) {
		if(v==gson[u]) continue;
		dsu(v);
	}
	if(u<=n){
		if(l*2-2<=dep[u])
			ans+=1ll*dp[dfn[u]+l*2-2];
		dp[dfn[u]]++;
	}
	for(int v:to[u]){
		if(v==gson[u]) continue;
		for(int j=(u<=n);j<=dep[v]&&j<=l*2-3;j+=2){
			if(l*2-1-1-1-j<=dep[u]) ans+=1ll*dp[dfn[u]+l*2-3-j]*dp[dfn[v]+j];
		}
		for(int j=0;j<=dep[v];j++){
			dp[dfn[u]+1+j]+=dp[dfn[v]+j];
		}
	}
}

int main(){
	freopen("sing.in","r",stdin);
	freopen("sing.out","w",stdout);
	sf("%d%d%d",&n,&m,&l);
	for(int i=1;i<=m;i++) {
		sf("%d%d",&u,&v);
		add(u,v),add(v,u);
	}
	cnt=0;
	tot=n;
	tarjan(1);
	find_g(1);
	cnt=0;
	dsu(1);
	pf("%lld\n",ans*2);
}
posted @ 2024-10-15 22:09  liyixin  阅读(6)  评论(0编辑  收藏  举报