题目描述(简化)
- 给定参数 a i , l i , r i a_i,l_i,r_i ai,li,ri。
- 然后有一个有向图,对于两个点 i , j i,j i,j,如果满足 i < j 且 l [ i ] ≤ a [ j ] ≤ r [ i ] i<j且l[i] \le a[j] \le r[i] i<j且l[i]≤a[j]≤r[i],那么就存在一条边 ( i , j ) (i,j) (i,j),否则不存在。
- 对于每一个点 i i i,求它的必经点有多少个。(如果它本来就是不联通的就请输出-1)
暴力
- 首先,我们可以看出这一个图显然是一个DAG。
- 紧接着,我们看一下问题——怎么越来越像求割点呢?
- 啊!tarjan!
- 恭喜你,错误,因为这是一个有向图,所以我们要用一种新算法。
- 考虑构造一棵树,它的根为1,使得对于每一个点,到根的路径上的每一个点都是对于这个点的必经点。
- 可以证明这样一个树有且仅有一种可能。
- 考虑一个一个加点。
- 假设这棵树已经有 i − 1 i-1 i−1个点,你要加第 i i i个点,那么这个点的父亲应该是谁呢?
- 这个有一个奇妙的结论,对于点集 { x ∣ ( x , i ) 存 在 } \{x|(x,i)存在\} {x∣(x,i)存在}把这些点在这棵树上所对应的点的lca找出来,就是第 i i i个点在书中的父亲了!
- 感性理解一下,发现这个其实还是比较显然的。
- 然后这棵树就建完了,时间复杂度 O ( n 2 l o g 2 n ) O(n^2log_2n) O(n2log2n),期望得分50分。
正解
- 既然 n ≤ 50000 n \le 50000 n≤50000那就不能暴力构图了。
- 但是我们发现对于那棵树,它的边数只有 n − 1 n-1 n−1,是可以建出来的。
- 所以我们用线段树来存储一下每一个点的父亲。
线段树
- 怎么求呢?
- 我们对于每一个点扫一遍,然后把区间 [ l , r ] [l,r] [l,r]扔进线段树里面,假设当前搜到第 i i i个点,很明显我们已经把前 i − 1 i-1 i−1个点加进去了,然后我们在线段树上查找 a i a_i ai,看看它所在的线段树上的位置的值——也就是所有连向它的点的lca。那么我们只需要在区间修改的时候,把区间原来的值 x x x和现在要加进去的点 y y y变成 l c a ( x , y ) lca(x,y) lca(x,y),然后就可以解出来了!。
- 因为普通线段树是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),加上求lca后就变成了 O ( n l o g 2 2 n ) O(nlog_2^2n) O(nlog22n),这个时间复杂度对于 50000 50000 50000来说绰绰有余。
特殊情况
- 如果一个点它的lca是0且它不是1,显然是-1。
- 如果一个点它在树上的深度最小的祖宗不是1,答案也是-1。
- 不知道要不要动态开点,然后就打了动态开点,以防万一。(后来认真看题目后发现不用)