【APIO 2018】铁人两项(圆方树)
题意大概是,求有多少三元组$(s,c,f)(s \neq c, c \neq f, s \neq f)$,满足从$s$到$f$有一条简单路径经过$c$。
得到结论:
- 点双中任意互不相同的三个点,必定存在一条简单路径依次经过这三个点。
- 显然,割点只能经过一次。
建出一棵圆方树,圆点的权值为$-1$,方点的权值为该点双中点的个数,那任意两个圆点之间可以作为它们中转点的个数就是它们在圆方树上路径的点权和。
具体来讲就是割点上只能经过一次,圆点设成$-1$是为了去重方便。
以前只写过点双缩树,这里写圆方树更方便,权且将这道题作为学习的例题吧。
建圆方树只要在Tarjan上稍作修改,这里给出建树的例子:
void Tarjan(int x, int fa) { sta[++top] = x; in[x] = -1; dfn[x] = low[x] = ++_clock; for (int i = las[x]; i; i = pre[i]) { if (to[i] == fa) continue; if (dfn[to[i]]) { low[x] = std::min(low[x], dfn[to[i]]); } else { Tarjan(to[i], x); low[x] = std::min(low[x], low[to[i]]); if (dfn[x] <= low[to[i]]) { xtr[x].push_back(++c_n); ++in[c_n]; for (int t = -1; t != to[i]; ) { t = sta[top--]; xtr[c_n].push_back(t); ++in[c_n]; } } } } }
(注:xtr为圆方树,in < 0代表圆点,否则代表了该方点中点双里点的个数)
之后这个题就好做了,统计的是所有圆点点对间路径长度的总和。
1 #include <cstdio> 2 #include <vector> 3 #include <iostream> 4 5 typedef long long LL; 6 const int N = 200005; 7 8 int n, m; 9 int dfn[N], low[N], in[N], siz[N], sta[N], top, _clock, c_n; 10 std::vector<int> xtr[N]; 11 LL ans, sum[N]; 12 13 int yun = 1, las[N], to[N << 1], pre[N << 1]; 14 inline void Add(int a, int b) { 15 to[++yun] = b; pre[yun] = las[a]; las[a] = yun; 16 } 17 18 void Tarjan(int x, int fa) { 19 sta[++top] = x; in[x] = -1; 20 dfn[x] = low[x] = ++_clock; 21 for (int i = las[x]; i; i = pre[i]) { 22 if (to[i] == fa) continue; 23 if (dfn[to[i]]) { 24 low[x] = std::min(low[x], dfn[to[i]]); 25 } else { 26 Tarjan(to[i], x); 27 low[x] = std::min(low[x], low[to[i]]); 28 if (dfn[x] <= low[to[i]]) { 29 xtr[x].push_back(++c_n); ++in[c_n]; 30 for (int t = -1; t != to[i]; ) { 31 t = sta[top--]; 32 xtr[c_n].push_back(t); ++in[c_n]; 33 } 34 } 35 } 36 } 37 } 38 39 void Dfs(int x) { 40 if (in[x] < 0) siz[x] = 1, sum[x] = in[x]; 41 for (int i = 0; i < (int)xtr[x].size(); ++i) { 42 int v = xtr[x][i]; 43 Dfs(v); 44 siz[x] += siz[v]; 45 sum[x] += (LL) siz[v] * in[x] + sum[v]; 46 } 47 } 48 void Calc(int x) { 49 if (in[x] < 0) ans += sum[x] - in[x]; 50 LL cnt = 0; 51 for (int i = 0; i < (int)xtr[x].size(); ++i) { 52 int v = xtr[x][i]; 53 ans += (LL) sum[v] * (siz[x] - (in[x] < 0) - siz[v]); 54 cnt += (LL) siz[v] * (siz[x] - (in[x] < 0) - siz[v]); 55 } 56 ans += (LL) cnt / 2 * in[x]; 57 for (int i = 0; i < (int)xtr[x].size(); ++i) { 58 int v = xtr[x][i]; 59 Calc(v); 60 } 61 } 62 63 int main() { 64 scanf("%d%d", &n, &m); 65 c_n = n; 66 for (int i = 1, x, y; i <= m; ++i) { 67 scanf("%d%d", &x, &y); 68 Add(x, y); Add(y, x); 69 } 70 for (int i = 1; i <= n; ++i) { 71 if (!dfn[i]) { 72 Tarjan(i, 0); 73 Dfs(i); 74 Calc(i); 75 } 76 } 77 printf("%lld\n", ans * 2); 78 79 return 0; 80 }
$\bigodot$技巧&套路:
- 圆方树的构建,圆方树上的统计技巧,可以用圆点的权值设成$-1$来去重。