[LOJ#2587][APIO2018]鐵人兩項(圓方樹+樹形dp)
Address
Solution
繼 APIO 2018 之後,圓方樹重出江湖!
圓方樹大概就是,將圖的每個點雙連通分量建一個方點,把連通分量裡的點全部連向這個方點,形成一棵樹。原圖中的點為圓點。
圓方樹能處理與圖連通性有關的許多問題。
回到原問題,問題相當於求對於所有的有序點對
:
分情況討論可能出現在
到
的簡單路徑上的點數(不包括
和
):
(1)
和
在同一個點雙內:為所在的點雙大小減
。
(2)
和
都是割點且不在同一點雙:
到
的路徑上(不包括
及
)的點雙大小之和(注:除
和
之外的割點只能被統計一次)。
(3)
和
不在同一點雙並且都不是割點:
到
的路徑上的所有點雙大小之和減去(路徑上的點雙個數加一)。
…
綜上,我們把方點的權值設為對應點雙大小,圓點的權值為
,那麼可能出現在
到
的簡單路徑上的點數(不包括
和
)就是圓方樹
到
的路徑上點的權值之和。
於是,我們把問題轉化成一棵樹上所有有序圓點對兩兩路徑權值和之和。
狀態:
表示
的子樹內無序圓點對的路徑權值和之和。
表示
到
的子樹內所有圓點的路徑權值和之和。
表示
的子樹內圓點的個數。
表示
點的權值。
轉移:
在列舉子樹
的過程中記錄下
和
表示
之前的子樹(不包括
)的 dp 值:
注意圖可能不連通,所以答案為:
複雜度
非常優秀。
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]