思路
题意
- 给定一个长为 的排列 和一个最初为空的大根堆
- 进行 次操作
- 取出堆顶放入 末尾
- 取出 开头放入堆
求最终得到的 的种类数
注意力惊人其实有点, 我说题解
性质
假设 在 中的位置为 , 在 中的位置为
因为 一定是堆中最后弹出来的数, 所以 前 个数的集合应该相同, 不难发现
证明
因为 总是在堆中最后出现, 那么每次 出现在 中, 堆一定是空的, 因此此时 对应数集相同
进一步处理, 剩下的部分中, 其最小值也满足上面的性质
同样可以类似上面的做
所以现在我们知道一个合法解的性质了
如果你执行 : 找到序列的最小值 \(\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]}\) 中数集相同
关于初始化 / 边界条件
显然应当初始化为
这种情况下整个区间会被跳过, 应当初始化为
如果不存在 $a_{[L, R]} = v$
我们直接找到当前区间 中的最小值, 然后查询 即可, 保证了区间中存在最小值
找到 \(a_{[L, R]}\) 的最小值位置 \(p\) , 枚举 \(b_{[L, R]}\) 中的最小值位置 \(q \in [p, R]\)
递归区间 \([L, p], (p, R]\) , 令 \(v \gets v + 1\) 表示已经处理完了 \(v\) 的位置, 以后 \(a\) 中 \(< v\) 的就不管了, 已经填好了
为什么要递归这两个区间
只有这样才能保证区间中满足「保证 和 中数集相同」
实现
#include <bits/stdc++.h>
#define int long longconst 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;
}
总结
找合法解的个数, 首先要尝试找到合法解的性质
一个新
排列类问题, 与大小有关系时, 尝试先找到 这些特殊点的位置, 然后按照特殊点分段
区间 \(\rm{dp}\) 往往可以使用记忆化搜索简化难度
记忆化搜索应当处理好初始化 / 边界条件