排列与不等号


数据规模:2≤N≤30002 \le N \le 3000,字符串 ss 长度为 N−1N-1,只含 < 与 >。

思路

只关心相对次序。 题目给的是排列,但约束全部来自相邻两数的大小关系,与具体数值无关。所以从前往后逐个确定元素时,只需要记住「已经用了几个数」以及「最后一个元素在已用的这些数里排第几小」,剩下的具体取值不影响后续决策。这正是所谓相对排名 DP。

状态。 设 dp[i][j]dp[i][j] 表示:长度为 ii 的前缀已经填好、与 ss 的前 i−1i-1 个字符相容,且第 ii 个元素在它所在的那 ii 个元素中排名第 jj(1≤j≤i1\le j\le i)的方案数。同一套「排名序列」对应多少种真实取值?对每个排名序列 q1,…,qiq_1,\dots,q_i(qtq_t 是第 tt 个元素在 {q1,…,qt}\{q_1,\dots,q_t\} 中的排名)恰好有一个相对序,也就对应 1..i1..i 的一个排列,因此按排名计数不会重复,也不会漏。

转移。 从 ii 扩展一位到 i+1i+1,新元素插入后,旧元素中排名 ≥\ge 某个值的会整体后移一位。分两种情形:

  • si=s_i = <(要求 pi<pi+1p_i < p_{i+1}):新元素排名为 jj 时,旧的最后一个元素排名 kk 必须严格小于 jj,即 k≤j−1k\le j-1。此时插在它前面,两者都保持原有排名。于是

dp[i+1][j]=∑k=1j−1dp[i][k].dp[i+1][j]=\sum_{k=1}^{j-1} dp[i][k].

  • si=s_i = >(要求 pi>pi+1p_i > p_{i+1}):新元素排名为 jj,则旧元素中排名 k≥jk\ge j 的都会被顶到 k+1k+1,仍然排在新元素后面;而排名 k<jk<j 的旧元素维持 kk,排在新元素前面。旧的最后一个元素排在新元素之后,当且仅当它的排名 k≥jk\ge j,故

dp[i+1][j]=∑k=jidp[i][k].dp[i+1][j]=\sum_{k=j}^{i} dp[i][k].

用前缀和压掉内层循环。 上面两式都是对 dp[i]dp[i] 的一段区间求和,其中区间端点是 jj 或 j−1j-1。先对 dp[i][1..i]dp[i][1..i] 求一遍前缀和 prepre,则

dp[i+1][j]={pre[j−1],si=<pre[i]−pre[j−1],si=>dp[i+1][j]=\begin{cases}pre[j-1], & s_i=\texttt{<}\\ pre[i]-pre[j-1], & s_i=\texttt{>}\end{cases}

每个 jj 都是 O(1)O(1),一层 ii 只需 O(i)O(i),总计 O(N2)O(N^2)。

边界。 dp[1][1]=1dp[1][1]=1(只有一个元素,排名恒为 1),其余为 0。注意 si=s_i= > 时 j=i+1j=i+1 的值为 pre[i]−pre[i]=0pre[i]-pre[i]=0,含义是「新元素成为最大」不可能满足 pi>pi+1p_i>p_{i+1},自动成立,不必特判。最终答案是 ∑j=1Ndp[N][j]\sum_{j=1}^{N}dp[N][j],因为所有排名都合法。

滚动数组。 第 i+1i+1 层只依赖第 ii 层的全部值,用两个一维数组交替即可,空间 O(N)O(N)。取模时减法要补 109+710^9+7 防止负数。

复杂度

时间 O(N2)O(N^2),空间 O(N)O(N)。N=3000N=3000 时约 4.5×1064.5\times10^6 次加法。

仓颉实现

排名下标从 1 开始,数组开 N+2N+2,pre[0]=0 作为前缀和的哨兵,避免判断越界。

查看仓颉实现
import std.env.*
import std.convert.*

main() {
    let reader = getStdIn()
    let n = Int64.parse(reader.readln().getOrThrow())
    let rs = reader.readln().getOrThrow().toRuneArray()
    let MOD: Int64 = 1000000007
    var dp = Array<Int64>(n + 2, { _ => 0 })
    let pre = Array<Int64>(n + 2, { _ => 0 })
    dp[1] = 1
    for (i in 1..n) {
        pre[0] = 0
        for (k in 1..=i) {
            pre[k] = (pre[k - 1] + dp[k]) % MOD
        }
        let ndp = Array<Int64>(n + 2, { _ => 0 })
        if (rs[i - 1] == r'<') {
            for (j in 1..=(i + 1)) {
                ndp[j] = pre[j - 1]
            }
        } else {
            for (j in 1..=(i + 1)) {
                ndp[j] = (pre[i] - pre[j - 1] + MOD) % MOD
            }
        }
        dp = ndp
    }
    var ans: Int64 = 0
    for (j in 1..=n) {
        ans = (ans + dp[j]) % MOD
    }
    println(ans)
}