洛谷4630 BZOJ APIO2018 铁人两项 圆方树 dp (圆方树学习笔记)

16 篇文章 0 订阅
4 篇文章 0 订阅

题目链接
题意:给你一个n个点m条边的无向图,求所有的能从s到c再到t的三元组个数,其中每个点在一条路径上至多经过一次。n,m1e5量级。

题解:
首先介绍一下圆方树。

还记得zyb大佬凭借圆方树在APIO拿AU并在SD二轮进队,近年来圆方树也成为了一个热门算法,于是还是很有必要学的。

圆方树的作用是把一个图变成一个树,并且能正确地记录一些信息。
把一个图变成一棵树的方法是我们首先利用tarjan,找到所有的点双连通分量。点双连通分量是值去掉图上任何一个点都不会改变连通性的连通分量。我们对于每一个点双连通分量,我们建一个新的点,表示整个点双,这种点成为方点,并且可能会在方点记录整个点双的信息,原图上的点成为圆点。特殊地,把两个点互相连通的也视作一个点双(这种似乎被称为广义圆方树)。对于这些点,我们的连边方式是把每一个圆点与他所在的每一个点双代表的方点连边。当然圆方树上的方点所维护的信息可能不包含在圆方树上处于这个方点父节点的位置的圆点的信息。
用网上别人的一张图,可能比较有助于理解。
在这里插入图片描述

这样对一个图建出圆方树之后我们就可以解决一些图上简单路径的问题了。似乎还经常用来解决一些仙人掌上的问题。圆方树题目经常需要在LCA处分圆点和方点分类讨论。

圆方树的一个性质是所有方点相连的点一定都是圆点,所有圆点相连的点一定都是方点。
另一个性质是无论以哪个点为根,圆方树的形态不变。

下面开始写这个题的做法。
我们会发现,如果在一个点双内选择两个点,那么任意地在点双里再选一个点作为中间点,一定能形成一个合法的三元组。如果两个不在同一个点双里的点的路径上经过了某个点双,那么把这个点双上的任意的一个点作为中间点形成的三元组都是合法的。那么我们考虑每一个点作为这个中间点对答案的贡献。
我们发现答案相当于对于每一个 i i i,求有多少 ( u , i , v ) (u,i,v) (u,i,v)是合法的。
我们对原图建出圆方树,树上圆点的点权是 − 1 -1 1,方点的点权是度数,也就是点双的大小,这里算是用到一点容斥的思想。这样之后 u u u v v v之间的点数是就变成了圆方树上两点之间的权值和。我们设 f [ i ] f[i] f[i] u u u的子树内无序圆点对的路径权值和之和, g [ i ] g[i] g[i] i i i i i i的子树内所有圆点的距离和之和, s u m [ i ] sum[i] sum[i]为以 i i i为根的子树内圆点的个数, s z [ i ] sz[i] sz[i]表示 i i i的权值。我们需要有顺序的转移来保证我们每个量转移前后的正确性。我们有以下转移式: f [ x ] = ∑ y ∈ s o n [ x ] f [ y ] + g [ x ] ∗ s u m [ y ] + g [ y ] ∗ s u m [ x ] f[x]=\sum_{y\in son[x]}f[y]+g[x]*sum[y]+g[y]*sum[x] f[x]=yson[x]f[y]+g[x]sum[y]+g[y]sum[x] g [ x ] = ∑ y ∈ s o n [ x ] g [ y ] + s u m [ y ] ∗ s z [ x ] g[x]=\sum_{y\in son[x]}g[y]+sum[y]*sz[x] g[x]=yson[x]g[y]+sum[y]sz[x] s u m [ x ] = [ x 是 圆 点 ] + ∑ y ∈ s o n [ x ] s u m [ y ] sum[x]=[x是圆点]+\sum_{y\in son[x]}sum[y] sum[x]=[x]+yson[x]sum[y]
由于图可能不连通,并且我们求的是无序点对(x,y)的答案,所以最终的结果是 ∑ i 是 某 棵 圆 方 树 的 根 f [ i ] ∗ 2 \sum_{i是某棵圆方树的根}f[i]*2 if[i]2

然后就做完了。复杂度是 O ( n + m ) O(n+m) O(n+m)

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,hed[400010],cnt,ct,hed2[500010],cnt2,z,dfn[400010];
int low[400010],sta[400010],tp;
long long ans,f[400010],sum[400010],g[400010],sz[400010];
struct node
{
	int to,next;
}a[400010],aa[400010];
inline void add(int from,int to)
{
	a[++cnt].to=to;
	a[cnt].next=hed[from];
	hed[from]=cnt;
}
inline void add2(int from,int to)
{
	aa[++cnt2].to=to;
	aa[cnt2].next=hed2[from];
	hed2[from]=cnt2;
}
inline void tarjan(int x)
{
	low[x]=dfn[x]=++z;
	sta[++tp]=x;
	for(int i=hed[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);
			if(dfn[x]<=low[y])
			{
				++ct;
				do
				{
					add2(ct,sta[tp]);
					add2(sta[tp],ct);
					--tp;
					sz[ct]++;
				}while(sta[tp+1]!=y);
				add2(ct,x);
				add2(x,ct);
				++sz[ct];
			}
		}
		else
		low[x]=min(low[x],dfn[y]);
	}
	
}
inline void dfs(int x,int fa)
{
	for(int i=hed2[x];i;i=aa[i].next)
	{
		int y=aa[i].to;
		if(y!=fa)
		dfs(y,x);
	}
	if(x<=n)
	{
		sum[x]=1;
		g[x]=sz[x];
	}
	for(int i=hed2[x];i;i=aa[i].next)
	{
		int y=aa[i].to;
		if(y!=fa)
		{
			f[x]+=f[y]+g[x]*sum[y]+g[y]*sum[x];
			g[x]+=g[y]+sum[y]*sz[x];
			sum[x]+=sum[y];
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	ct=n;
	for(int i=1;i<=m;++i)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	for(int i=1;i<=n;++i)
	sz[i]=-1;
	for(int i=1;i<=n;++i)
	{
		if(!dfn[i])
		{
			tarjan(i);
			dfs(i,0);
			ans+=f[i]*2;
		}
	}
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值