1. 程式人生 > >[LOJ#2587][APIO2018]鐵人兩項(圓方樹+樹形dp)

[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]