[LOJ#2587][APIO2018]铁人两项(圆方树+树形dp)

Address

洛谷P4630
LOJ#2587

Solution

继 APIO 2018 之后,圆方树重出江湖!
圆方树大概就是,将图的每个点双连通分量建一个方点,把连通分量里的点全部连向这个方点,形成一棵树。原图中的点为圆点。
圆方树能处理与图连通性有关的许多问题。
回到原问题,问题相当于求对于所有的有序点对 ( u , v ) , u v (u,v),u\ne v
( u , v ) u v u v \sum_{(u,v)}可能出现在u到v的简单路径上的点数,不包括u和v
分情况讨论可能出现在 u u v v 的简单路径上的点数(不包括 u u v v ):
(1) u u v v 在同一个点双内:为所在的点双大小减 2 2
(2) u u v v 都是割点且不在同一点双: u u v v 的路径上(不包括 u u v v )的点双大小之和(注:除 u u v v 之外的割点只能被统计一次)。
(3) u u v v 不在同一点双并且都不是割点: u u v v 的路径上的所有点双大小之和减去(路径上的点双个数加一)。

综上,我们把方点的权值设为对应点双大小,圆点的权值为 1 -1 ,那么可能出现在 u u v v 的简单路径上的点数(不包括 u u v v )就是圆方树 u u v v 的路径上点的权值之和。
于是,我们把问题转化成一棵树上所有有序圆点对两两路径权值和之和。
状态:
f [ u ] f[u] 表示 u u 的子树内无序圆点对的路径权值和之和。
g [ u ] g[u] 表示 u u u u 的子树内所有圆点的路径权值和之和。
s u m [ u ] sum[u] 表示 u u 的子树内圆点的个数。
v a l [ u ] val[u] 表示 u u 点的权值。
转移:
s u m [ u ] = [ u ] + v s o n [ u ] s u m [ v ] sum[u]=[u是圆点]+\sum_{v\in son[u]}sum[v]
g [ u ] = v s o n [ u ] { g [ v ] + s u m [ v ] × v a l [ u ] } g[u]=\sum_{v\in son[u]}\{g[v]+sum[v]\times val[u]\}
在枚举子树 v v 的过程中记录下 g [ u ] g[u]' s u m [ u ] sum[u]' 表示 v v 之前的子树(不包括 v v )的 dp 值:
f [ u ] + = f [ v ] + g [ u ] × s u m [ v ] + g [ v ] × s u m [ u ] f[u]+=f[v]+g'[u]\times sum[v]+g[v]\times sum'[u]
注意图可能不连通,所以答案为:
2 × i f [ i ] 2\times\sum_{i是某个连通块的根}f[i]
复杂度 O ( n + m ) O(n+m) 非常优秀。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
#define Edge2(u) for (int e = adj2[u], v; e; e = nxt2[e]) if ((v = go2[e]) != fu)
using namespace std;

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

typedef long long ll;
const int N = 1e5 + 5, D = N << 1, M = D << 1;
int n, m, ecnt, nxt[M], adj[N], go[M], dfn[N], low[N], T, stk[N],
top, nm, ecnt2, nxt2[M], adj2[D], go2[M], sze[D], sum[D];
ll f[D], g[D], ans;

int Min(int a, int b) {return a < b ? a : b;}

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}

void add_edge2(int u, int v)
{
	nxt2[++ecnt2] = adj2[u]; adj2[u] = ecnt2; go2[ecnt2] = v;
	nxt2[++ecnt2] = adj2[v]; adj2[v] = ecnt2; go2[ecnt2] = u;
}

void dfs(int u)
{
	dfn[stk[++top] = u] = low[u] = ++T;
	Edge(u)
		if (!dfn[v])
		{
			dfs(v);
			low[u] = Min(low[u], low[v]);
			if (dfn[u] <= low[v])
			{
				nm++;
				do
				{
					add_edge2(nm, stk[top--]);
					sze[nm]++;
				} while (stk[top + 1] != v);
				add_edge2(nm, u); sze[nm]++;
			}
		}
		else low[u] = Min(low[u], dfn[v]);
}

void dp(int u, int fu)
{
	Edge2(u) dp(v, u);
	sum[u] = u <= n; g[u] = u <= n ? sze[u] : 0;
	Edge2(u)
		f[u] += f[v] + g[u] * sum[v] + g[v] * sum[u],
		g[u] += g[v] + 1ll * sum[v] * sze[u],
		sum[u] += sum[v];
}

int main()
{
	int i, x, y;
	n = nm = read(); m = read();
	For (i, 1, n) sze[i] = -1;
	For (i, 1, m) x = read(), y = read(),
		add_edge(x, y);
	For (i, 1, n) if (!dfn[i])
		dfs(i), dp(i, 0), ans += f[i] << 1;
	cout << ans << endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/82811426
今日推荐