首页 > 其他分享 >树上最值路径 题解

树上最值路径 题解

时间:2024-10-02 22:11:28浏览次数:6  
标签:min int 题解 路径 dfs max Path 树上 最值

题意简述

给你一棵 \(n\) 个结点的树,编号为 \(1 \sim n\),求有多少路径 \(\operatorname{Path}(u \rightarrow v)\),满足 \(u = \max \{ x \mid x \in \operatorname{Path}(u \rightarrow v) \}\),\(v = \min \{ x \mid x \in \operatorname{Path}(u \rightarrow v) \}\)。

\(n \leq 2 \times 10^6\)。

题目分析

考虑从小到大的顺序枚举 \(u\),去掉 \(\max\) 的限制,因为 \(u\) 必然是之前的点中的 \(\max\),\(\min\) 从大到小扫同理。在加入 \(u\) 之前,这棵树是若干个联通块。\(u\) 和其相邻的联通块之中的任意一点 \(v\),都能保证 \(u = \max \{ x \mid x \in \operatorname{Path}(u \rightarrow v) \}\)。我们考虑并查集缩点,每次在一棵新的树上,把代表联通块的点和 \(u\) 之间连一条边,并将其在并查集上的父亲设为 \(u\)。这实际上是一个重构树的过程,这个新树有一个很好的性质,那就是当且仅当 \(v\) 是 \(u\) 在新树上的所有后代,使得 \(u = \max \{ x \mid x \in \operatorname{Path}(u \rightarrow v) \}\)。我们将一个奇怪的最值限定,变成了树上祖先后代的限定。我们将这棵树称作 \(\max\) 树。

我们现在有了两棵重构树,分别满足 \(\min\) 和 \(\max\) 的限制。然后就要考虑如何统计答案了,也就是有多少对 \((u, v)\),满足在 \(\max\) 树上,\(u\) 是 \(v\) 的祖先,在 \(\min\) 树上,\(v\) 是 \(u\) 的祖先。我们还是考虑枚举,在 \(\min\) 树上枚举一个 \(u\),查询有多少 \(\min\) 树上 \(u\) 的祖先 \(v\),满足在 \(\max\) 树上 \(v\) 是 \(u\) 的后代。祖先的限制可以用 dfs 栈的思想,用一个数据结构实时维护 dfs 栈中的元素。后者查询后代操作,可以用 dfs 序 + 树状数组区间查询。那么 dfs 栈就是树状数组的单点加操作。

时间复杂度:\(\Theta(n (\alpha(n) + \log n))\)

代码

#include <cstdio>
#include <iostream>
using namespace std;

const int N = 2000010;

struct Graph {
    struct node {
        int to, nxt;
    } edge[N << 1];
    int tot, head[N];
    inline void add(int u, int v) {
        edge[++tot] = {v, head[u]};
        head[u] = tot;
    }
    inline node & operator [] (int x) {
        return edge[x];
    }
} xym, yzh1, yzh2;

int n;
long long ans;

int fa[N];
int get(int x) {
    return fa[x] == x ? x : fa[x] = get(fa[x]);
}

int tree[N];
inline void modify_add(int p) {
    for (int i = p; i <= n; i += i & -i)
        ++tree[i];
}
inline void modify_sub(int p) {
    for (int i = p; i <= n; i += i & -i)
        --tree[i];
}
inline int query(int p) {
    int res = 0;
    for (int i = p; i; i &= i - 1)
        res += tree[i];
    return res;
}

int L[N], R[N], timer;
void dfs(int now) {
    L[now] = ++timer;
    for (int i = yzh1.head[now]; i; i = yzh1[i].nxt)
        dfs(yzh1[i].to);
    R[now] = timer;
}

void redfs(int now) {
    ans += query(R[now]) - query(L[now] - 1);
    modify_add(L[now]);
    for (int i = yzh2.head[now]; i; i = yzh2[i].nxt)
        redfs(yzh2[i].to);
    modify_sub(L[now]);
}

signed main() {
	#ifndef XuYueming
	freopen("charity.in", "r", stdin);
	freopen("charity.out", "w", stdout);
	#endif
    scanf("%d", &n);
    for (int i = 1, f; i <= n; ++i) {
        scanf("%d", &f), fa[i] = i;
        if (f == 0) continue;
        xym.add(i, f), xym.add(f, i);
    }
    for (int u = 2; u <= n; ++u) {
        for (int i = xym.head[u]; i; i = xym[i].nxt) {
            int v = xym[i].to;
            if (v > u) continue;
            yzh2.add(u, v = get(v));
            fa[v] = u;
        }
    }
    for (int i = 1; i <= n; ++i) fa[i] = i;
    for (int u = n - 1; u >= 1; --u) {
        for (int i = xym.head[u]; i; i = xym[i].nxt) {
            int v = xym[i].to;
            if (v < u) continue;
            yzh1.add(u, v = get(v));
            fa[v] = u;
        }
    }
    dfs(1), redfs(n), printf("%lld", ans);
    return 0;
}

标签:min,int,题解,路径,dfs,max,Path,树上,最值
From: https://www.cnblogs.com/XuYueming/p/18445117

相关文章

  • 题解:CF2014D Robert Hood and Mrs Hood
    差分,每次差分将\(\max(1,l-d+1)\)加\(1\),将\(r+1\)位置减\(1\)。然后前缀和求原数组,再遍历一下求最小值和最大值即可。代码:#include<bits/stdc++.h>usingnamespacestd;intmain(){ intt; cin>>t; while(t--){ intn,d,k; cin>>n>>d>>k;......
  • 题解:CF2009E Klee's SUPER DUPER LARGE Array!!!
    设\(m\)为\(a_1+\dots+a_i\),\(n\)为\(a_{i+1}+\dots+a_n\)。我们可以使用二分查找来搜索\(i\),使得\(m-n\)为最小的负数。如果我们移动到\(i+1\),则此时\(m-n\)为最小的整数。答案是两种情况下的最小绝对值。代码:#include<bits/stdc++.h>usingnamespacestd;pair<......
  • 题解:CF2009D Satyam and Counting
    比较容易观察的一道题,但是场上不开longlong见祖宗了。由于这题的\(x\)最大值比较小,所以我们可以直接存每个坐标是否有点。有两种三角形符号条件:存在两个点\((x,0),(x,1)\),可以观察到任意的其它点都可以成为第三点。有三个点为\((x,0),(x+1,1),(x+2,0)\)或\((x,1),(x+1......
  • 题解:CF2009C The Legend of Freya the Frog
    比较一眼的题目,场切了。分别考虑\(x\)和\(y\)。在\(x\)方向上我们需要的跳跃次数是\(\lceil\frac{x}{k}\rceil\),在\(y\)方向上我们需要的跳跃次数是\(\lceil\frac{y}{k}\rceil\)。考虑下面的两种情况:\(\lceil\frac{y}{k}\rceil\geq\lceil\frac{x}{k}\rceil......
  • 题解:P11137 [APC001] B - Checker
    注意到每个字符串长度相同,所以我们可以按照题意逐个遍历小K的题目和所有题库里的题目,统计相同位置字符相同的个数,如果大于\(\left\lceil\frac{k}{2}\right\rceil\),这两个题目就是重题。代码:#include<bits/stdc++.h>usingnamespacestd;boolc(stringa,stringb){in......
  • 题解:CF2014C Robin Hood in Town
    分享一种二分答案做法。先特判\(n=1\),\(n=2\),开始就有超过一半的人不高兴的情况。然后二分\(x\),计算新的和,新的平均值,如果有超过一半的人不高兴,就更新答案。代码:#include<bits/stdc++.h>usingnamespacestd;intmain(){ intt; cin>>t; while(t--){ intn; cin>>n......
  • 题解:SP7973 ACPC10E - Sometimes, a penalty is good!
    比较简单的一道数学题。思路:计算小组赛的比赛总数。longlongstage1=G*T*(T-1)/2;每组有\(T\)个队伍,每个队伍都需要与其他\(T-1\)个队伍比赛,共有\(T\cdot(T-1)\)场比赛。共有\(G\)组,因此小组赛总比赛数为\(\frac{G\cdotT\cdot(T-1)}{2}\)。计算进入......
  • 题解:SP10242 ACPC11D - Dice on a Board
    思路递归生成所有的可能的筛子朝向,用DFS标记所有可达的位置,用dijkstra计算从起始位置到目标位置的最优路径,并确定在移动过程中能够获得的最大分数。generate函数generate用于生成所有可能的骰子朝向排列,\(mask\)作为参数,用于表示哪些数字已经被使用。使用二进制压缩。......
  • 题解:P9954 [USACO20OPEN] Cowntact Tracing B
    考虑暴力。枚举让每头牛都当一次“零号病人”和\(K\)的所有组合,模拟感染的过程,检查得出的病人是否和给出的一样即可。代码:#include<bits/stdc++.h>usingnamespacestd;boolinfectedd[101];intN,cowx[251],cowy[251];boolcheck(intpatient_zero,intK){ boolinfect......
  • 题解:SP9934 ALICE - Alice and Bob
    状态表示:使用两个变量来表示当前游戏的状态:\(a\)表示包含\(1\)个石子的堆的数量,\(b\)表示包含多于\(1\)个石子的堆的可操作次数。游戏策略:从包含多个石子的堆中取走一个石子,这会减少\(b\)。从包含\(1\)个石子的堆中取走一个石子,这会减少\(a\)。合......