【LOJ2587】「APIO2018」铁人两项

本文介绍了一种利用网络流和圆方树解决点双连通分量中特定路径计数问题的方法,通过巧妙地构造网络流模型证明了一个关键引理,并采用树形DP实现了高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目链接】

【思路要点】

  • 首先我们来证明点双连通分量的一个性质。
  • 引理:在一个点双连通分量中,给定任意三个不同的点\(a\),\(b\),\(c\),一定存在一条从\(a\)到\(c\)的,经过每个点至多一次的简单路径经过了\(b\)。
  • 证明:考虑网络流。在原图中存在无向边的点对之间建立无向边,容量为1;对每个点拆点限流,容量为1;由\(a\)和\(c\)向汇点连边,容量为1;由源点向\(b\)连边,容量为2。显然,原命题等价于源点到汇点的最大流等于2。显然源点到汇点的最大流不超过2,现在我们来证明它大于1。考虑最小割,我们发现割去一条点对之间的无向边等价于删去原图中的一条边,割去一条限流边等价于删去原图中的一个点,由于原图点双连通,我们无法只割去一条容量为1的边使得源点到汇点不连通,因此最小割大于1,原命题得证。
  • 那么,我们建立圆方树,枚举\(b\),考虑如何计算合法的\((a,c)\)点对数。
  • 由上面证明的引理,我们只需要保证\(a\)到\(b\)的路径上和\(c\)到\(b\)的路径上没有相同的必经的圆点就可以保证\((a,b,c)\)是一个合法的点对。也即在直接与\(b\)由方点相连的圆点的子树中,\(a\)和\(c\)应当属于不同的子树。
  • 简单树形DP即可。
  • 时间复杂度\(O(N+M)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
vector <int> a[MAXN], b[MAXN];
int n, m, oldn, top, Stack[MAXN];
int timer, dfn[MAXN], low[MAXN];
int cnt, size[MAXN];
long long ans, tans[MAXN];
void work(int pos, int fa) {
	size[pos] = pos <= oldn;
	for (unsigned i = 0; i < b[pos].size(); i++)
		if (b[pos][i] != fa) {
			work(b[pos][i], pos);
			size[pos] += size[b[pos][i]];
		}
	if (pos <= oldn) {
		ans += (cnt - 1ll) * (cnt - 1ll);
		for (unsigned i = 0; i < b[pos].size(); i++)
			if (b[pos][i] != fa) ans -= tans[b[pos][i]];
	} else {
		for (unsigned i = 0; i < b[pos].size(); i++)
			if (b[pos][i] != fa) tans[pos] += 1ll * size[b[pos][i]] * size[b[pos][i]];
		long long tmp = 1ll * (cnt - size[pos]) * (cnt - size[pos]);
		for (unsigned i = 0; i < b[pos].size(); i++)
			if (b[pos][i] != fa) ans -= tans[pos] - 1ll * size[b[pos][i]] * size[b[pos][i]] + tmp;
	}
}
void tarjan(int pos) {
	Stack[++top] = pos; cnt++;
	dfn[pos] = low[pos] = ++timer;
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (dfn[a[pos][i]] == 0) {
			tarjan(a[pos][i]);
			chkmin(low[pos], low[a[pos][i]]);
			if (low[a[pos][i]] == dfn[pos]) {
				int tmp = 0, cmt = 1; n++;
				while (tmp != a[pos][i]) {
					tmp = Stack[top--];
					b[n].push_back(tmp);
					b[tmp].push_back(n);
					cmt++;
				}
				b[n].push_back(pos);
				b[pos].push_back(n);
			}
		} else chkmin(low[pos], dfn[a[pos][i]]);
}
int main() {
	read(n), read(m), oldn = n;
	for (int i = 1; i <= m; i++) {
		int x, y;
		read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	for (int i = 1; i <= oldn; i++)
		if (dfn[i] == 0) {
			cnt = 0;
			tarjan(i);
			work(i, 0);
		}
	writeln(ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值