title
BZOJ 5463
LUOGU 4630
Description
比特镇的路网由 m 条双向道路连接的 n 个交叉路口组成。
最近,比特镇获得了一场铁人两项锦标赛的主办权。这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程。
比赛的路线要按照如下方法规划:
1、先选择三个两两互不相同的路口 s, c 和 f ,分别作为比赛的起点、切换点(运动员在长跑到达这个点后,骑自行车前往终点)、终点。
2、选择一条从 s 出发,经过 c 最终到达 f 的路径。考虑到安全因素,选择的路径经过同一个点至多一次。
在规划路径之前,镇长想请你帮忙计算,总共有多少种不同的选取 s, c 和 f 的方案,使得在第 2 步中至少能设计出一条满足要求的路径。
Input
第一行包含两个整数 n 和 m ,分别表示交叉路口和双向道路的数量。
接下来 m 行,每行两个整数 v_i, u_i 。表示存在一条双向道路连接交叉路口 v_i, u_i (1 <= v_i, u_i <= n, v_i != u_i)。
保证任意两个交叉路口之间,至多被一条双向道路直接连接。
n<=1e5, m<=2e5
Output
输出一行,包含一个整数,表示能满足要求的不同的选取s,c和f的方案数
Sample Input
4 3
1 2
2 3
3 4
Sample Output
8
Source
analysis
圆方树就是,将图的每个点双连通分量建一个方点,把连通分量里的点全部连向这个方点,形成一棵树,原图中的点为圆点。
圆方树能处理与图连通性有关的许多问题。
啊,概念讲完了,下面回归本问题(一句话题意):
对于所有的有序点对 \((x,y),x\not= y\),\(\sum_{(x,y)}\) 可能出现在 \(x\) 到 \(y\) 的简单路径上的点数,不包括 \(x\) 和 \(y\) 。
分情况讨论可能出现在 \(x\) 到 \(y\) 的简单路径上的点数(不包括 \(x\) 和 \(y\) ):
- \(x\) 和 \(y\) 在同一个点双内:为所在的点双大小减 \(2\) 。
- \(x\) 和 \(y\) 都是割点且不在同一点双: \(x\) 到 \(y\) 的路径上(不包括 \(x\) 及 \(y\) )的点双大小之和(注:除 \(x\) 和 \(y\) 之外的割点只能被统计一次)。
- \(x\) 和 \(y\) 不在同一点双并且都不是割点: \(x\) 到 \(y\) 的路径上的所有点双大小之和减去(路径上的点双个数加一)。
…
综上,我们把方点的权值设为对应点双大小,圆点的权值为 \(-1\) ,那么可能出现在 \(x\) 到 \(y\) 的简单路径上的点数(不包括 \(x\) 和 \(y\) )就是圆方树 \(x\) 到 \(y\) 的路径上点的权值之和。
于是,我们把问题转化成一棵树上所有有序圆点对两两路径权值和之和。
- 状态:
\(f[x]\) 表示 \(x\) 的子树内无序圆点对的路径权值和之和。
\(g[x]\) 表示 \(x\) 到 \(x\) 的子树内所有圆点的路径权值和之和。
\(sum[x]\) 表示 \(x\) 的子树内圆点的个数。
\(val[x]\) 表示 \(x\) 点的权值。 - 转移:
\(sum[x]=[x是圆点]+\sum_{y\in son[x]}sum[y]\)
\(g[x]=\sum_{y\in son[x]}\{g[y]+sum[y]\times val[x]\}\)
在枚举子树 \(y\) 的过程中记录下 \(g[x]\) 和 \(sum[x]\) 表示 \(y\) 之前的子树(不包括 \(y\) )的 \(dp\) 值:\(f[x]+=f[y]+g[x]×sum[y]+g[y]×sum[x]\)
注意图可能不连通,所以答案为:\(2\times\sum_{i是某个连通块的根}f[i]\)。
总复杂度 \(O(n+m)\)。
参考资料:xyz32768。
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=4e5+10;
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
template<typename T>inline void write(T x)
{
if (!x) { putchar('0'); return ; }
if (x<0) putchar('-'), x=-x;
T num=0, ch[20];
while (x) ch[++num]=x%10+48, x/=10;
while (num) putchar(ch[num--]);
}
struct Graph
{
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
Graph()
{
memset(head,0,sizeof(head));
len=0;
}
inline void add(int x,int y)
{
ver[++len]=y,Next[len]=head[x],head[x]=len;
ver[++len]=x,Next[len]=head[y],head[y]=len;
}
} G1,G2;
int dfn[maxn],low[maxn],id;
int Stack[maxn],top;
int val[maxn],belong[maxn],siz[maxn],tot;
inline void tarjan(int x)
{
dfn[x]=low[x]=++id;
Stack[++top]=x;
for (int i=G1.head[x]; i; i=G1.Next[i])
{
int y=G1.ver[i];
if (!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
if (low[y]>=dfn[x])
{
int k;
++tot;
do
{
k=Stack[top--];
++siz[tot];
G2.add(tot,k);
} while (k!=y);
++siz[tot];
G2.add(tot,x);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
int n,m,sum[maxn];
long long ans,f[maxn],g[maxn];
inline void dfs(int x,int fa)
{
sum[x]=(x<=n);
g[x]=x<=n?-1:0;
for (int i=G2.head[x]; i; i=G2.Next[i])
{
int y=G2.ver[i];
if (y==fa) continue;
dfs(y,x);
f[x]+=f[y]+g[x]*sum[y]+g[y]*sum[x];//f[x] 表示 x 的子树内无序圆点对的路径权值和之和。
g[x]+=g[y]+1ll*siz[x]*sum[y];//g[x]表示 x 到 x 的子树内所有圆点的路径权值和之和。
sum[x]+=sum[y];//sum[x]表示 x 的子树内圆点的个数。
}
}
int main()
{
read(n);read(m);tot=n;
memset(siz,-1,(n+1)<<2);
for (int i=1,x,y; i<=m; ++i) read(x),read(y),G1.add(x,y);
for (int i=1; i<=n; ++i)
if (!dfn[i]) tarjan(i),dfs(i,0),ans+=f[i]<<1;
write(ans),puts("");
return 0;
}
本文深入探讨了圆方树算法,一种处理图连通性问题的有效方法。通过将图的双连通分量转化为方点,原图点转化为圆点,形成树状结构,解决了在特定条件下计算不同点对间路径上点数的问题。文章详细讲解了算法原理,提供了状态转移方程,并附带代码实现。
454

被折叠的 条评论
为什么被折叠?



