LGP4630 [APIO 2018] 铁人两项 学习笔记

LGP4630 [APIO 2018] 铁人两项 学习笔记

Luogu Link

前言

从这题入门圆方树吧。

题意简述

给你一个 \(n\)\(m\) 边的无向无权图。我们规定一个三元组 \((s,c,f)\) 合法,当且仅当 \(s,c,f\) 两两不同,且存在一条 \(s\to c\to f\) 的路径满足经过每个点至多一次。问有多少合法三元组。

本题保证无自环或重边。\(n\le 10^5\)

做法解析

这是一个什么问题呢?这是一个无向图上的、对于一定范围内的所有路径(或者说较为多的连通性)有所询问、而且数据范围不接受 \(n^2\) 的问题。再加上这道题有一个“经过每个点至多一次”,考虑圆方树。

圆方树是什么?众所周知,对一个(连通的)无向图求点双后按点双缩点,会缩出一棵树。而圆方树约等于是往这棵树上挂上了原图的结点所形成。具体来说,圆方树由原图的所有结点(称为圆点)与所有代表点双的点(称为方点)组成,圆点与其所在点双的方点连边。

但我们为什么需要圆方树呢?因为 \(s\to f\) 的所有路径的并等于 \(s\to f\) 的所有点双的并,所以这个问题等价于我们要统计每一对 \((s,f)\) 其间路径上所有点双的并的大小。而圆方树相当于对点双缩点结构的提炼。

证明:
\(s\) 走到 \(f\) 的路径肯定都得从 \(s\) 所在的点双 \(u\) 走到 \(f\) 所在的点双 \(v\),而又因为点双缩点后整个图的结构是一棵树,且一个点只能经过一次,所以 \(s\to f\) 的路径要是跑到了缩点后 \(u\to v\) 简单路径的外面,那想绕回来就要重复经过一个割点两次,不合法。这意味着合法的 \(s\to f\) 路径的点可以且只可以经过 \(u\to v\) 路径上所有点双。

考虑构造:令方点权值为其所代表的点双的大小,圆点权值为 \(-1\)(为了去除重复计算的割点),这样子,这对 \((s,f)\) 的答案就是在圆方树上 \(s\to f\) 简单路径的权值和了(\(s\)\(f\) 本身不应该算进去,不过这个刚好被 \(s\)\(f\) 自己 \(-1\) 的权值去重了)。

但是我们要算的是“每一对 \((s,f)\)”的答案。怎么办呢?答案是做一遍 \(\text{dfs}\),详见下文。

代码实现

由定义可以看出,圆方树的大小显然不超过 \(2n\)。所以数组开两倍。

csbn 是用来记录原图上当前极大连通块的大小的。因为题目并没有保证全图连通。

另外,我们来分析一下这段 \(\text{dfs}\) 到底如何工作。

void dfs(int u,int f){
    siz[u]=(u<=N);
    for(int v : Gr2[u]){
        if(v==f)continue;dfs(v,u);
        ans+=2ll*siz[u]*siz[v]*val[u],siz[u]+=siz[v];
    }
    ans+=2ll*siz[u]*(csbn-siz[u])*val[u];
}

\(\text{dfs}\) 相当于把这棵树当成有根了的,下文方便叙述一些。

首先,这个 2ll 的系数是因为对于每一对 \((s,c,f)\),也有一对 \((f,c,s)\) 可以作为答案。

然后这个 siz[u] 显然记的是 \(u\) 子树内圆点的数量。

ans+=2ll*siz[u]*siz[v]*val[u],siz[u]+=siz[v]; 这一行显然是在计算形如 \((v_1,u,v_2)\) 类的答案,而 ans+=2ll*siz[u]*(csbn-siz[u])*val[u]; 是在计算形如 \((v,u,anc)\)\((anc,u,v)\) 类的答案。

乘上的这个系数 val[u],作为方点的时候其正确性很好理解,毕竟既然要经过当前这个方点,那它统辖的圆点肯定都能作为 \(c\),所以要有这个系数。不过圆点的 val[u]-1,这个容斥系数是因为一个割点可能同时属于多个点双,如果两种方案的区别仅仅在于统计时某个圆点所属的方点不同,那就要去重。

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e5+5;
int N,M,X,Y;lolo ans;
vector<int> Gr1[MaxN];
void addudge1(int u,int v){
    Gr1[u].push_back(v);
    Gr1[v].push_back(u);
}
int dfn[MaxN],low[MaxN],tot,csbn;
int stk[MaxN],ktp,nln,val[MaxN<<1];
vector<int> Gr2[MaxN<<1];
void addudge2(int u,int v){
    Gr2[u].push_back(v);
    Gr2[v].push_back(u);
}
void tarjan(int u,int f){
    dfn[u]=low[u]=++tot,val[u]=-1;
    stk[++ktp]=u,csbn++;int vc=0;
    for(int v : Gr1[u]){
        if(!dfn[v]){
            vc++,tarjan(v,u);
            minner(low[u],low[v]);
            if(low[v]>=dfn[u]){
                nln++;
                while(stk[ktp+1]!=v)addudge2(stk[ktp--],nln),val[nln]++;
                addudge2(u,nln),val[nln]++;
            }
        }
        else if(v!=f)minner(low[u],dfn[v]);
    }
    if(!f&&!vc)val[++nln]++,addudge2(u,nln);
}
int siz[MaxN<<1];
void dfs(int u,int f){
    siz[u]=(u<=N);
    for(int v : Gr2[u]){
        if(v==f)continue;dfs(v,u);
        ans+=2ll*siz[u]*siz[v]*val[u],siz[u]+=siz[v];
    }
    ans+=2ll*siz[u]*(csbn-siz[u])*val[u];
}
int main(){
    readis(N,M);nln=N;
    for(int i=1;i<=M;i++)readis(X,Y),addudge1(X,Y);
    for(int i=1;i<=N;i++)if(!dfn[i])ktp=csbn=0,tarjan(i,0),dfs(i,0);
    writi(ans);
    return 0;
}
posted @ 2025-07-30 10:12  矞龙OrinLoong  阅读(8)  评论(0)    收藏  举报