题目描述

小 Y 在学树论时看到了有关二叉树的介绍: 在计算机科学中, 二叉树是每个结点最多有两个子结点的有序树. 通常子结点被称作 “左孩子” 和 “右孩子”. 二叉树被用作二叉搜索 树和二叉堆. 随后他又和他人讨论起了二叉搜索树.

什么是二叉搜索树呢? 二叉搜索树首先是一棵二叉树. 设 key [p] 表示结点 p 上的数值. 对于其中的每个结点 p, 若其存在左孩子 lch, 则 key [p]\(\gt\) key [lch]; 若其存在右孩子 rch, 则 key [p]\(\lt\) key [rch]; 注意, 本题中的二叉搜索树应满足对于所有结点, 其左子树中的 key 小于 当前结点的 key, 其右子树中的 key 大于当前结点的 key.

小 Y 与他人讨论的内容则是, 现在给定一棵二叉树, 可以任意修改结点的数值. 修改一个结点的数值算作一次修改, 且这个结点不能再被修改. 若要将其变成一棵二叉搜索树, 且任意时刻结点的数值必须是整数 (可以是负整数或 0), 所要的最少修改次数.

相信这一定难不倒你! 请帮助小 Y 解决这个问题吧.

输入格式

第一行一个正整数 n 表示二叉树结点数. 结点从 1~ n 进行编号.

第二行 n 个正整数用空格分隔开, 第 i 个数 ai 表示结点 i 的原始数值.

此后 n-1 行每行两个非负整数 fa, ch, 第 i+2 行描述结点 i+1 的父亲编号 fa, 以及父子关系 ch, (ch=0 表示 i+1 为左儿子, ch=1 表示 i+1 为右儿子).

结点 1 一定是二叉树的根.

输出格式

仅一行包含一个整数, 表示最少的修改次数.

样例输入 1

3
2 2 2
1 0
1 1

样例输出 1

2

样例输入 2

7
6 2 5 3 4 7 2
1 1
2 1
3 1
4 1
5 1
6 1

样例输出 2

4

数据范围

20%:\(n \leq 10 , a_i \leq 100\).

40%:\(n \leq 100 , a_i \leq 200\)

60%:\(n \leq 2000\).

100%:\(n \leq 10^5 , a_i \lt 2^{31}\).

Explanation

就是一道巨水 DP 题. 简单的来讲, 我们可以先求出这棵树的先序遍历, 然后就把一个在树上的复杂问题变成了一个在序列上瞎搞的题目......

然后问题就变成一个类 最长非降子序列的问题. 这样看来, 将每一个数加上当前所在位置的哈希 (所谓哈希, 就是 \(n - i\)) . 最后就变成了求一个序列的最长非降子序列.

这道题难道不是很简单嘛~

但是写了很久还是没有 AC. 为什么呢? 因为我又 作死写了一个 Splay 然后调了一整下午三个小时都不停地死循环+TLE+WA+RE+CE (233)~

其他的做法还有:

  1. 神奇优化线段树 (先离散化, 然后在线段树上区间最大区间修改), 就是我的 AC 做法.
  2. 玄学二分做法 ((@MisakiHanayo 的做法)
  3. 还有比较正常的堆~

Splay 能干的事, 没有什么不是 Heap 不能干的事.

世界上没有什么问题不是 Heap 不能干的事. 如果有, 那就是可持久化 Heap.

Source Code


#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>

using namespace std;
typedef long long lli;
const int maxn = 200100;

class SegmentTree
{
public:
    int n, lbnd[maxn], rbnd[maxn], vmx[maxn], lazy[maxn];
    void build_tree(int n_) {
        n = 1; while (n < n_) n *= 2;
        for (int i = 1; i <= n; i++) {
            lbnd[i + n - 1] = rbnd[i + n - 1] = i;
            vmx[i + n - 1] = lazy[i + n - 1] = 0; }
        for (int i = n - 1; i >= 1; i--) {
            lbnd[i] = lbnd[i * 2];
            rbnd[i] = rbnd[i * 2 + 1];
            vmx[i] = lazy[i] = 0; }
        return ; }
    void dispatch_lazy(int p) {
        lazy[2 * p] = max(lazy[2 * p], lazy[p]);
        lazy[2 * p + 1] = max(lazy[2 * p + 1], lazy[p]);
        vmx[p] = max(vmx[p], lazy[p]);
        lazy[p] = 0; }
    int query(int p, int l, int r) {
        if (l == lbnd[p] && r == rbnd[p])
            return max(vmx[p], lazy[p]);
        dispatch_lazy(p);
        if (r <= rbnd[2 * p]) return query(2 * p, l, r);
        else if (l >= lbnd[2 * p + 1]) return query(2 * p + 1, l, r);
        return max(query(2 * p, l, rbnd[2 * p]), query(2 * p + 1, lbnd[2 * p + 1], r)); }
    void change(int p, int l, int r, int v) {
        if (l == lbnd[p] && r == rbnd[p]) {
            lazy[p] = max(lazy[p], v);
            return ; }
        dispatch_lazy(p);
        if (r <= rbnd[2 * p]) change(2 * p, l, r, v);
        else if (l >= lbnd[2 * p + 1]) change(2 * p + 1, l, r, v);
        else change(2 * p, l, rbnd[2 * p], v), change(2 * p + 1, lbnd[2 * p + 1], r, v);
        vmx[p] = max(max(vmx[2 * p], lazy[2 * p]), max(vmx[2 * p + 1], lazy[2 * p + 1])); }
public:
    int query(int l, int r) {
        if (l > r) return 0;
        return query(1, l, r); }
    void change(int l, int r, int v) {
        if (l > r) return ;
        change(1, l, r, v); }
} st;

struct srtr { int a, b; };
bool cmp(srtr a, srtr b) { return a.a < b.a; };
class DistributionReduction
{
public:
    int n;
    int arr[maxn];
    srtr srt[maxn];
    int eval(int n) {
        this->n = n;
        for (int i = 1; i <= n; i++) {
            srt[i].a = arr[i];
            srt[i].b = i;
        }
        sort(srt + 1, srt + 1 + n, cmp);
        int ncnt = 0;
        for (int i = 1; i <= n; i++)
            arr[srt[i].b] = (srt[i].a > srt[i - 1].a) ? (++ncnt) : (ncnt);
        return ncnt; }
} dr;

int n, root, arr_cnt;
int ch[maxn][2];
int val[maxn], arr[maxn];
int dp[maxn];

void dfs_tree(int p)
{
    if (p == 0)
        return ;
    // If this is not a null sequence, make some modifications
    dfs_tree(ch[p][0]);
    arr[++arr_cnt] = val[p];
    dfs_tree(ch[p][1]);
    return ;
}

int main(int argc, char** argv)
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &val[i]);
    for (int i = 2, a, b; i <= n; i++) {
        scanf("%d%d", &a, &b);
        ch[a][b] = i;
    }
    // Done inputting data, now processing tree into a sequence.
    dfs_tree(1);
    // Pre-processing a sequence.
    // Obviously the datum must be added with its successive number.
    for (int i = 1; i <= n; i++)
        arr[i] -= i - n;
    // Pre-saving data orientation with sorting techniques
    for (int i = 1; i <= n; i++) dr.arr[i] = arr[i];
    int m = dr.eval(n); // Sorting and evaluating...
    for (int i = 1; i <= n; i++) arr[i] = dr.arr[i];
    // Dynamic programming (with segment tree optimisation)
    st.build_tree(m);
    for (int i = 1; i <= n; i++) {
        // dp[i] = 1;
        // for (int j = 1; j < i; j++)
        //     if (arr[j] <= arr[i])
        //         dp[i] = max(dp[i], dp[j] + 1);
        int j = st.query(1, arr[i]);
        dp[i] = j + 1;
        st.change(arr[i], m, dp[i]);
    }
    int res = n;
    for (int i = 1; i <= n; i++)
        res = min(res, n - dp[i]);
    printf("%d\n", res);
    return 0;
}

以下是 WA 掉的代码. 虽然不会 CE, 但是请诸位大神慎入~


#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <set>
#include <algorithm>

using namespace std;
typedef long long lli;
const int maxn = 200100;
const int infinit = 1000000007;

class SplayTree
{
public:
    int ch[maxn][2], parent[maxn], root, ncnt, n;
    int val[maxn], dat[maxn], maxx[maxn];
    #define lc(x) ch[x][0]
    #define rc(x) ch[x][1]
    #define par(x) parent[x]
    int makenode(int q, int v, int w)
    {
        int p = ++ncnt; n++;
        lc(p) = rc(p) = 0;
        par(p) = q;
        val[p] = v;
        dat[p] = maxx[p] = w;
        return p;
    }
    void updatemaxx(int p)
    {
        maxx[p] = p > 2 ? dat[p] : 0;
        if (lc(p)) maxx[p] = max(maxx[p], maxx[lc(p)]);
        if (rc(p)) maxx[p] = max(maxx[p], maxx[rc(p)]);
        return ;
    }
    void rotate(int p)
    {
        int q = par(p), g = par(q), x = p == rc(q);
        // Relink connexions between nodes
        ch[q][x] = ch[p][!x], par(ch[q][x]) = q;
        ch[p][!x] = q, par(q) = p;
        par(p) = g;
        if (g) ch[g][rc(g) == q] = p;
        // Update data values
        updatemaxx(p);
        updatemaxx(q);
        if (g)
            updatemaxx(g);
        return ;
    }
    void splay(int p, int t)
    {
        for (int q = 0; (q = par(p)) && q != t; rotate(p))
            if (par(q) && par(q) != t)
                rotate((p == lc(q)) == (q == lc(par(q))) ? q : p);
        if (t == 0) root = p;
        return ;
    }
    int suc(int p)
    {
        if (!rc(p)) { while (p == rc(par(p))) p = par(p); p = par(p); }
        else { p = rc(p); while (lc(p)) p = lc(p); }
        return p;
    }
    int find(int x)
    {
        int p = root;
        while (p && (lc(p) || rc(p))) {
            if (x < val[p])
                p = lc(p);
            else if (x == val[p])
                return p;
            else
                p = rc(p);
        }
        return p;
    }
    void insert(int v, int w)
    {
        int lp = find(v), rp = suc(lp);
        splay(rp, 0);
        splay(lp, root);
        int c = makenode(lp, v, w);
        rc(lp) = c;
        updatemaxx(lp);
        updatemaxx(rp);
        return ;
    }
    void modify(int v, int w)
    {
        printf("modify %d %d\n", v, w);
        int p = find(v);
        if (!p)
            return ;
        if (val[p] != v) {
            insert(v, w);
            return ;
        }
        splay(p, 0);
        dat[p] = w;
        updatemaxx(p);
        return ;
    }
    int query_max(int l, int r)
    {
        int lp = find(l), rp = find(r);
        printf("query max %d %d %d %d\n", l, r, lp, rp);
        splay(rp, 0);
        splay(lp, root);
        // Return data values
        int res = maxx[rc(lp)];
        if (dat[lp] >= l)
            res = max(res, dat[lp]);
        if (dat[rp] <= r)
            res = max(res, dat[rp]);
        return res;
    }
    void buildtree()
    {
        n = ncnt = 0;
        root = makenode(0, -2, 0);
        rc(root) = makenode(root, infinit, 0);
        maxx[1] = maxx[2] = infinit;
        par(rc(root)) = root;
        return ;
    }
} sp;

int n, root, arr_cnt;
int ch[maxn][2];
int val[maxn], arr[maxn];
int dp[maxn];

void dfs_tree(int p)
{
    if (p == 0)
        return ;
    // If this is not a null sequence, make some modifications
    dfs_tree(ch[p][0]);
    arr[++arr_cnt] = val[p];
    dfs_tree(ch[p][1]);
    return ;
}

int main(int argc, char** argv)
{
    // sp.buildtree();
    // sp.modify(0, 0);
    // sp.modify(12, 1);
    // sp.modify(7, 1);
    // sp.modify(9, 1);
    // sp.modify(6, 1);
    // sp.modify(6, 1);
    // sp.modify(8, 1);
    // sp.modify(2, 1);
    // sp.query_max(1, 7);
    // return 0;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &val[i]);
    for (int i = 2, a, b; i <= n; i++) {
        scanf("%d%d", &a, &b);
        ch[a][b] = i;
    }
    // Done inputting data, now processing tree into a sequence.
    dfs_tree(1);
    // Pre-processing a sequence.
    // Obviously the datum must be added with its successive number.
    for (int i = 1; i <= n; i++)
        arr[i] -= i - n;
    // As the splay tree has problems...
    sp.buildtree();
    sp.modify(0, 0);
    sp.insert(infinit - 1, 0);
    set<int> st; st.insert(-0);
    // Now checking incrementation status
    for (int i = 1; i <= n; i++) {
        dp[i] = 1;
        // for (int j = 1; j < i; j++)
        //     if (arr[j] <= arr[i])
        //         dp[i] = max(dp[i], dp[j] + 1);
        int j = arr[i]; // Find exact data, smaller than arr[i]
        int v = sp.query_max(0, j);
        printf("dping %d %d %d\n", i, j, v);
        dp[i] = v + 1;
        sp.modify(arr[i], dp[i]);
        st.insert(-arr[i]);
    }
    // Processing best selection
    int res = n;
    for (int i = 1; i <= n; i++)
        res = min(res, n - dp[i]);
    printf("%d\n", res);
    return 0;
}