例1 luogu4630 [APIO2018] 铁人两项
建圆方树.
如果固定了s,f, 合法的c集合是圆方树上s->f路径上经过的点双的并.
给所有点赋权,方点点权为点双大小,圆点点权为-1.
那么合法的c数量便是s->f路径点权和.
问题变成了求所有圆点两两之间的路径点权和的和.
dfs(x)时,answer+=通过x的次数*val[x].
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
inline int read() {
int x = 0, f = 0; char ch = getchar();
while (!isdigit(ch)) f = ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return f ? -x : x;
}
const int N = 200010, M = 1100010;
int n, m, rt;
int dfn[N], low[N], num, cnt;
int stk[N], top;
bool cut[N];
vector<int> dcc[N];
struct Tree {
int head[N], nex[M], ver[M], tot = 1;
void add(int x, int y) {
ver[++tot] = y; nex[tot] = head[x]; head[x] = tot; }
};
Tree tr, yf;
int siz[N];
ll val[N], answer;
void tarjan(int x) {
dfn[x] = low[x] = ++num;
stk[++top] = x;
int flag = 0, son = 0;
for (int i = tr.head[x]; i; i = tr.nex[i]) {
int y = tr.ver[i];
if (!dfn[y]) {
++son;
tarjan(y);
low[x] = min(low[x], low[y]);
if (dfn[x] <= low[y]) {
++flag;
if (flag > 1 || x != rt) cut[x] = 1;
++cnt;
// printf("x = %d, y = %d, ++cnt; \n", x, y);
int z;
do {
z = stk[top--];
dcc[cnt].push_back(z);
} while (z != y);
dcc[cnt].push_back(x);
}
} else low[x]