题目
题目背景
O
n
e
I
n
D
a
r
k
\sf OneInDark
OneInDark 站在
O
U
Y
E
\sf OUYE
OUYE 的面前,接受神的洗礼。汝必先弃置肉身,方可入众神之门。
话音刚落,
O
n
e
I
n
D
a
r
k
\sf OneInDark
OneInDark 感觉自己开始慢慢缩成一团,越缩越紧,最后以一个诡异而扭曲的姿势瘫倒在地。他的呼吸变得缓慢,目光变得柔和。这是一个凄美的结尾。
许多年后,考古学家发掘出了 O n e I n D a r k \sf OneInDark OneInDark 的遗体化石。他们无不惊讶地发现, O n e I n D a r k \sf OneInDark OneInDark 的每一根骨头都是弯曲的!这也是人类历史上第一个,被别人 卷死 的案例。
在同一地点,人们还发现了遗迹化石,上面赫然写着一句话:吾之魂需一肉身,正如植物需要泥土。吾之头脑好似玻璃,记忆好似光。
题目描述
给出一个树,求有多少个点对
x
,
y
(
x
<
y
)
x,y\;(x<y)
x,y(x<y) 满足:树上
x
,
y
x,y
x,y 两点的唯一简单路径中,经过的所有点的编号都在
[
x
,
y
]
[x,y]
[x,y] 内。
数据范围与提示
n
⩽
2
×
1
0
6
n\leqslant 2\times 10^6
n⩽2×106 。但是显然不是线性的。
思路
第一步,膜拜 O U Y E \sf OUYE OUYE,轻易地做出了牺牲者 O n e I n D a r k \sf OneInDark OneInDark 做不出的题。当众人努力扛起补题的重担时, O U Y E \sf OUYE OUYE 正哼着小曲,做着自己一眼就看得出来的 N O I \rm NOI NOI 题目。卷爷 O U Y E \sf OUYE OUYE 是问题转化之神!
那么题目背景试图告诉我们什么呢?弃置原有的肉身 抛开原有的树形结构。原有的树形结构实在是一个可恶的误导信息:一旦想到点分治,或者一切跟树上路径有关的东西,你就必死无疑。
问题显然等价于,求有多少条路径满足路径上经过的点的编号 min , max \min,\max min,max 在端点处取得。有一个非常恐怖但是完全正确的想法是,不去求出路径。单纯考虑求出路径 min \min min 。这比较类似于求出路径上边权的 min \min min 值,可以用最小生成树;所以求点权的 min \min min 其实也是类 克鲁斯卡尔重构树。
而最值在端点处,等价于删掉端点(删掉最值点)后路径不断开。放在重构树上去理解,就是一条路径的最值点不能在路径中间。而路径的最值点就是 l c a lca lca,如果要 l c a lca lca 在路径的端点,其实就是 x x x 是 y y y 的祖先。竟然会转化出这么简洁的关系式,难以想象!
现在我们建出两棵重构树 T 1 , T 2 T_1,T_2 T1,T2,问多少个点对 ( x , y ) (x,y) (x,y) 满足 T 1 T_1 T1 中 x x x 是 y y y 的祖先, T 2 T_2 T2 中 y y y 是 x x x 的祖先。在 T 2 T_2 T2 上 d f s \rm dfs dfs 即可取出所有 T 2 T_2 T2 上 x x x 的祖先 y y y,需要快速求出 T 1 T_1 T1 中哪些 y y y 可以在 x x x 子树内。显然求出 d f s \rm dfs dfs 序后用芬威克树即可维护, O ( n log n ) \mathcal O(n\log n) O(nlogn) 且常数很小,代码实现极为简单。
代码
#include <cstdio> // XJX yyds!!!
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
void writeint(int x){
if(x > 9) writeint(x/10);
putchar(char((x%10)^48));
}
const int MAXN = 6000006;
struct Edge{
int to, nxt;
Edge() = default;
Edge(int _to,int _nxt):
to(_to),nxt(_nxt){ }
};
Edge e[MAXN<<1];
int head[MAXN], cntEdge;
void addEdge(int a,int b){
e[cntEdge] = Edge(b,head[a]);
head[a] = cntEdge ++;
}
namespace UFS{
int fa[MAXN];
void init(int n){
rep(i,1,n) fa[i] = i;
}
int find(int a){
return (fa[a] != a) ? (fa[a] = find(fa[a])) : a;
}
void merge(int a,int b){
fa[find(a)] = find(b);
}
}
int st[MAXN], ed[MAXN], dfsClock;
void scan(int x,const int &n){
st[x-n] = ++ dfsClock;
for(int i=head[x]; ~i; i=e[i].nxt)
scan(e[i].to,n);
ed[x-n] = dfsClock;
}
int c[MAXN]; llong ans;
void dfs(int x,const int &n){
for(int i=ed[x-2*n]; i; i&=(i-1)) ans += c[i];
for(int i=st[x-2*n]-1; i; i&=(i-1)) ans -= c[i];
for(int i=st[x-2*n]; i<=n; i+=(i&-i)) ++ c[i];
for(int i=head[x]; ~i; i=e[i].nxt) dfs(e[i].to,n);
for(int i=st[x-2*n]; i<=n; i+=(i&-i)) -- c[i];
}
int main(){
int n = readint();
memset(head+1,-1,(3*n)<<2);
readint(); // fa(1) = 0
for(int i=2,f; i<=n; ++i){
f = readint();
addEdge(i,f), addEdge(f,i);
}
UFS::init(n);
rep(i,1,n) for(int j=head[i]; ~j; j=e[j].nxt){
int p = UFS::find(e[j].to);
if(e[j].to < i && p != UFS::find(i))
addEdge(i+n,p+n), UFS::merge(p,i);
}
UFS::init(n);
drep(i,n,1) for(int j=head[i]; ~j; j=e[j].nxt){
int p = UFS::find(e[j].to);
if(e[j].to > i && p != UFS::find(i))
addEdge(i+n*2,p+2*n), UFS::merge(p,i);
}
scan(n<<1,n), dfs(n<<1|1,n);
printf("%lld\n",ans);
return 0;
}
后记
又过了很多年,人们才知道, O n e I n D a r k \sf OneInDark OneInDark 只是 O U Y E \sf OUYE OUYE 转生的牺牲品。 O U Y E \sf OUYE OUYE 究竟内卷多久了?没有人知道。从人类有文字记载的历史开始, O U Y E \sf OUYE OUYE 就一直在卷,并且将一直卷到今天,卷到明天,卷到人类文明不复存在。凭借转生之术, O U Y E \sf OUYE OUYE 必将永世长存!
为何转生需要牺牲品呢?因为: When you take another’s life, you take your own. \text{When you take another's life, you take your own.} When you take another’s life, you take your own.