铁人两项
求满足存在 x → y x \rightarrow y x→y 和 y → z y \rightarrow z y→z 的不相交简单路径的有序点对 ( x , y , z ) (x, y, z) (x,y,z) 的方案数。
即,选择的路径只经过同一个点至多一次。
线性做法。
广义圆方树
可以解决一些“每个点至多经过一次”的问题。
对于任意无向图,都可以建出它的广义圆方树。
tarjan 求出每个点双连通分量,然后同一点双的所有点(圆点)向代表这个点双的点(方点)连边。
只考虑这些新连的边,发现在 广义圆方树 中,只有圆点和方点连的边,且这些新连的边恰好构成一棵树。
那么就在考虑点双的问题中,把原来的一般无向图转化为树,然后就可以使用一些树上问题的方法。
本题做法
钦定 x x x 和 z z z,则此时的方案数为:排除 x x x 和 z z z 两个端点后,所有 x → z x \rightarrow z x→z 的简单路径上的点的并,也就是经过的所有点双的并。( y y y 会被经过,当且仅当:对于任意路径,会从 y y y 所在的点双的某一点进入,同一点双的另一点离开。)
构造广义圆方树后,若给方点赋权 v a l val val 为点双大小,圆点赋权 v a l = − 1 val = -1 val=−1,则答案为圆方树上 x x x 到 z z z 路径(含两端点)的权值和。
经过的圆点会被两边的方点各统计一次,应当减掉;端点权值是 − 1 -1 −1,抵消掉所连方点的自己那一个贡献。完美!
广义圆方树板子:
int dfn[MAXN], low[MAXN], cnt;
int sta[MAXN], top, num;
int val[MAXN], tot;void tarjan(int x) {dfn[x] = low[x] = ++cnt;sta[++top] = x;val[x] = -1;++tot;for (int i = g1.head[x]; i; i = g1.nxt[i]) {int y = g1.ver[i];if (!dfn[y]) {tarjan(y);low[x] = min(low[x], low[y]);if (low[y] >= dfn[x]) {int u; ++num;do {u = sta[top--];g2.add(u, n+num), g2.add(n+num, u);++val[n+num];} while (u != y);g2.add(x, n+num), g2.add(n+num, x);++val[n+num];}} else low[x] = min(low[x], dfn[y]);}
}
考虑不枚举 x x x 和 z z z,枚举 y y y,计算贡献。
只需要计算 y y y 被不同的路径经过的次数。
因为 x x x 和 z z z 只考虑圆点,所以子树中一个圆点的贡献为 1 1 1,一个方点的贡献应当为 0 0 0。
然后对于一个 y y y,采用先用当前乘之前的总和,然后把当前累加进总和的方法。
最后当然还要考虑来自父节点的那一部分贡献。
int siz[MAXN]; LL ans;
void solve(int x, int pa) {siz[x] = x <= n;for (int i = g2.head[x]; i; i = g2.nxt[i]) {int y = g2.ver[i];if (y == pa) continue;solve(y, x);ans += 1ll * siz[x] * siz[y] * val[x];siz[x] += siz[y];}ans += 1ll * siz[x] * (tot - siz[x]) * val[x];
}
因为是有序三元组,所以最终答案需要乘 2 2 2。