PERIODNI - Periodni 题解 & 笛卡尔树讲解 & 树状背包讲解

PERIODNI - Periodni 题解 & 笛卡尔树讲解 & 树状背包讲解

前置知识笛卡尔树

笛卡尔树每个节点具有标号和 \(w_i\) ,两个属性 ,标号满足二叉搜索树的性质,而 \(w_i\) 满足小根堆的性质。

可以证明,给你标号和 \(w_i\) ,有且仅有一种形状的树满足笛卡尔树的性质。

构造笛卡尔树

这里提供一种最优秀的 O(n) 做法。

第一步,如果 \(w\) 和编号都是递增的按照编号将节点一个个挂上去,构造一条右链。形如:

设当前节点为 \(i\) ,这条链的末尾节点为 \(t\),当\(w_i < w_t\) ,在这条链上找一个 \(w_j\le w_i\) 作为节点 \(j\) 的右儿子,节点 \(j\) 本身的右儿子变成了节点 \(i\) 的左儿子。

例如,我们现在插入一个 \(w = 6\) 的点:

现在这个右链就变成了 \(1\rightarrow2\rightarrow5\) 这条链。

一直进行这样的操作就可以了。

但如果真的直接建树的话复杂度期望 \(O(n\log n)\) 甚至可以被卡成 \(O(n^2)\)(虽然可以过这个题)

所以我们用单调栈模拟实现。

单调栈实现

我们单调栈里面装的东西就是模拟这个右链。

当进入一个节点 a

  • 令这个节点在栈中弹出的最后一个节点是 x ,那么 a 的左儿子为 x。

  • 令这个节点在栈中的下面一个的节点是 y ,那么 y 的右儿子为 a。

可以结合下面图和上面的构造理解一下。

代码

void input(){
    cin>>n>>m;
    for(int i = 1; i <= n; ++i){
        cin>>v[i];
        while(top && s[top].v > v[i]){
            --top;
            if((!top) || (top && s[top].v <= v[i])){
                tr[i].ls = s[top + 1].num;
            }
        }
        if(top)tr[s[top].num].rs = i;
        s[++top] = (node){i,v[i]};
    }
    for(int i = 1;i <= n; ++i){
        jl[tr[i].ls] = 1;
        jl[tr[i].rs] = 1;
    }
    for(int i = 1;i <= n; ++i){
        if(!jl[i]) rt = i;
    }
}

题目

[题面](PERIODNI - Periodni - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

解析

建模

我们把这个奇怪的图形看出几个矩形拼接而成,把每个矩形标号。

我们用每个矩形最矮的那一列来代表这个矩形

例如矩形 B 由 1,2 列构成,最矮的是 2 列,用 2 代替,类似的 D 用 4 来代替。

就变成了:

这像是一个树的结构,把他画出来看看。

把当前行的高度作为 \(w\), 好像是笛卡尔树,我们来证明一下。

首先,儿子节点的高度一定比父亲节点高,\(w\) 一定更大,一定满足小根堆的性质。

按照左边左儿子,右边右儿子的规律建的树,一定满足二叉搜索树的性质。

容易观察到两个性质:

  • 某矩阵所代表 \(i\)\(fa_i\)的高度差就是矩阵的长度。

  • \(i\) 为根的子树大小就是矩阵的宽度。

当时我有这样的疑惑,如果树长这样有好像有 3 个儿子,阁下该如何应对。

我们把这个笛卡尔树画出来。

惊奇的发现它还是满足上面说的性质。

直接建树就行了。

树上背包

我们用树上背包 dp 。设 \(f[i][j]\) 表示以 i 根的子树的所有矩形中放了 j 个点的方案数。

方程

\[f[i][j]=\sum_{k=0}^jf[i][j-k]\times f[son_i][k] \]

最后还要计算当前矩形放数字的方案数。

总的来说,先把左儿子放入更新,再把右儿子放入更新,最后用自己更新。

这是什么意思?

例子:假设现在我们考虑到 x,x 左儿子放 0,1,2 个,x 右儿子放 0,1,2 个,那在 x 区域就可以放 0,1,2,3,4 个。

树上背包的思想和背包很像,其实他省略了一维 k 表示当前考虑了 \(1\sim k\) 的儿子节点得到的结果,就像背包中我们省略了一维当前考虑了 \(1\sim i\) 的物品。

那么如果当前矩阵是 \(n\times m\) 的,选 \(k\) 个点,方案数是多少呢?

选 k 行 k 列,然后排列,显然:

\[\binom n k\times \binom m k \times k! \]

一定记住倒序枚举,省去了子树一维,注意枚举边界。

posted @ 2023-07-20 17:45  He_Zi  阅读(14)  评论(0编辑  收藏  举报