演唱会
演唱会
圆方树、点双连通分量、树上启发式合并、长链剖分
题意
给你一个 \(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);
}
本文来自博客园,作者:liyixin,转载请注明原文链接:https://www.cnblogs.com/liyixin0514/p/18468611