Loading

2.11 CW 模拟赛 T1. 刀等数学

思路

题意

  • 给定一个长为 nn 的排列 aa 和一个最初为空的大根堆
  • 进行 2n2n 次操作
    • 取出堆顶放入 bb 末尾
    • 取出 aa 开头放入堆

求最终得到的 bb 的种类数

注意力惊人其实有点, 我说题解

性质

假设 11aa 中的位置为 pp , 在 bb 中的位置为 qq
因为 11 一定是堆中最后弹出来的数, 所以 a,ba, bqq 个数的集合应该相同, 不难发现 pqp \leq q

证明

因为 11 总是在堆中最后出现, 那么每次 11 出现在 bb 中, 堆一定是空的, 因此此时 a,ba, b 对应数集相同

进一步处理, 剩下的部分中, 其最小值也满足上面的性质
同样可以类似上面的做

所以现在我们知道一个合法解的性质了
如果你执行 : 找到序列的最小值 \(\to\) 切割 \(\to\) 找到序列的最小值 \(\to\) 切割 \(\to \cdots\) , 那么每次切下来的那一段中, 对应的 \(a, b\) 数集相同, 也就是说那一段中 \(a, b\) 对应的最小值相同
更一般的, 我们可以把每次切割产生的两个序列都如上处理, 仍然符合性质, 这提示我们使用区间 \(\rm{dp}\)

但是这样怎么统计数量?
考虑仿照分段的过程, 进行一个记忆化的搜索

我们每次处理当前 \([l, r], v\) , 表示考虑 \([L, R]\) 区间中 \(< v\) 的部分已经被填过了, 保证 \(a_L, a_R \geq v\) , 且存在 \(a_i \in [L, R] = v\) , 保证 \(a_{[L, R]}\)\(b_{[L, R]}\) 中数集相同

关于初始化 / 边界条件

  • lrl \geq r
    显然应当初始化为 11
  • a[L,R]<va_{[L, R]} < v
    这种情况下整个区间会被跳过, 应当初始化为 11

细节

  • 如果不存在 a[L,R]=va_{[L, R]} = v
    我们直接找到当前区间 [L,R][L,R] 中的最小值, 然后查询 fL,r,minf_{L, r, \min} 即可, 保证了区间中存在最小值

找到 \(a_{[L, R]}\) 的最小值位置 \(p\) , 枚举 \(b_{[L, R]}\) 中的最小值位置 \(q \in [p, R]\)

递归区间 \([L, p], (p, R]\) , 令 \(v \gets v + 1\) 表示已经处理完了 \(v\) 的位置, 以后 \(a\)\(< v\) 的就不管了, 已经填好了

为什么要递归这两个区间

只有这样才能保证区间中满足「保证 a[L,R]a_{[L, R]}b[L,R]b_{[L, R]} 中数集相同」


这种分拆类问题还是太神奇了, 做了这么多道题仍然没有什么想法
所以以这道题为例, 对这一种问题进行一些简单的分析

  • 定义合法情况, 要求输出一组合法情况 / 合法情况的最值问题 / 求方案数
    • 往往利用 \(\rm{dp}\) , 结合约束处理当前方案数
      • 关注构造方案 / 顺序
      • 关注本质重复的转移是否存在
      • 由合法情况导出的答案
        • 先考虑最终答案的表达式 \((\)合法解的构造方案\()\) , 基础上进行 \(\rm{dp}\)
    • 先找到一组合法解, 然后在基础上进行调整
    • 找到所有情况统一的构造方案
    • 模拟操作情况
      • 找到最好开销, 注意最大和最小
      • 先找到一组合法解, 然后观察性质
题意

  • 给定一个长为 nn 的排列 aa 和一个最初为空的大根堆
  • 进行 2n2n 次操作
    • 取出堆顶放入 bb 末尾
    • 取出 aa 开头放入堆

求最终得到的 bb 的种类数

找不到什么好处理的构造方案
因此先构造出最终的 \(b\) , 看看有什么好的性质

发现一旦 \(1\) 进入堆里面, 那么其弹出时, 堆一定是空的, 所以之前的数集相同
所以可以分成两个部分, 之前之后的数集都相同, 当前点的约束就是要钦定 \(1\)\(b\) 中的那个位置
对两边的数列可以同理处理, 只是把 \(1\) 换成最小值

但是怎么处理两边的数集
不难发现数集可以简单的用 \(a[L, p]\)\(a[p, R]\) 统计, 然后简单处理出当前序列的最小值即可

唯一一个细节就在数集相同的部分是左闭右闭的, 莫名增加了实现难度

实现

#include <bits/stdc++.h>
#define int long long

const int MAXN = 105;
const int MOD = 8580287;
namespace calc {
    int add(int a, int b) { return a + b > MOD ? a + b - MOD : a + b; }
    int mul(int a, int b) { return (a * b * 1ll) % MOD; }
    int sub(int a, int b) { return a - b < 0 ? a - b + MOD : a - b; }
    void addon(int &a, int b) { a = add(a, b); }
    void mulon(int &a, int b) { a = mul(a, b); }
} using namespace calc;

int n;
int a[MAXN];
int f[MAXN][MAXN][MAXN];

int dfs(int l, int r, int x) {
    if (l >= r) return 1;
    if (f[l][r][x] != -1) return f[l][r][x];

    bool useless = true; for (int i = l; i <= r; i++) if (a[i] >= x) useless = false;
    if (useless) return f[l][r][x] = 1;

    int mn = n + 1; for (int i = l; i <= r; i++) if (a[i] >= x) mn = std::min(mn, a[i]);
    if (mn > x) return f[l][r][x] = dfs(l, r, mn);
    int ans = 0;
    bool flag = false;
    for (int i = l; i <= r; i++) {
        if (a[i] == x) flag = true;
        if (a[i] >= x && flag) addon(ans, mul(dfs(l, i, x + 1), dfs(i + 1, r, x + 1)));
    }
    return f[l][r][x] = ans;
}

signed main()
{
    scanf("%lld", &n); memset(f, -1, sizeof(f));
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    printf("%lld", dfs(1, n, 1));
    return 0;
}

总结

找合法解的个数, 首先要尝试找到合法解的性质

一个新 trick

排列类问题, 与大小有关系时, 尝试先找到 1,n1, n 这些特殊点的位置, 然后按照特殊点分段

区间 \(\rm{dp}\) 往往可以使用记忆化搜索简化难度
记忆化搜索应当处理好初始化 / 边界条件

posted @ 2025-02-11 19:18  Yorg  阅读(19)  评论(0)    收藏  举报