看数据范围可以大致确定是区间dp。
设 \(f_{l,r}\) 表示只考虑区间 \([l,r]\) 的方案数(只考虑区间 \([l,r]\) 是指只考虑 \([l_i,r_i]\in [l,r]\) 的 \(x_i\) 方案数)。
转移枚举 \([l,r]\) 中最大的 \(p_i\) 即可。但是这样会导致一个问题:不同的 \(p\) 可能对应相同的 \(x\)。
我们不妨规定,\(x\) 相同的不同 \(p\) 排列,只计算最大值出现的那个位置最靠前的一个,最大值出现位置相同的只计算次大值出现的位置最靠前的一个……实际上就是把最大值,次大值,次次大值一直到最小值的出现位置保留字典序最小的那个。
对于转移,假设最大值在 \(k\) 位置,\([k+1,r]\) 区间没有影响,但假设 \([l,k-1]\) 区间的最大值在 \(j\) 位置,如果没有跨过 \([j,i]\) 区间且在 \([l,r]\) 以内的线段(这里的线段指每对 \([l_i,r_i]\))的话,我们可以交换 \(p_i,p_j\) 的值做到更小的字典序。这就给了 \([l,k-1]\) 区间的最大值位置一个下限。
因此重新设计状态 \(f_{l,r,k}\) 表示只考虑区间 \([l,r]\),最大值位置 \(\ge k\) 的方案数。为了转移,令 \(s_{l,r,k}\) 表示 \([l,r]\) 区间内,横跨 \(k\) 区间的线段中左端点最小的那一个。
\[f_{l,r,k}=f_{l,r,k+1}+f_{l,k-1,s_{l,r,k}}\;f_{k+1,r,k+1} \]总结:这道题告诉我们,在需要对某个序列 \(a\) 进行计数时,如果直接计数比较困难但容易对另一个序列 \(b\) 进行计数,且多个 \(b\) 序列可能对应一个 \(a\) 时,可以考虑给 \(b\) 加一些限制,常见的如字典序最小。
其实这个思想不一定只在序列上有用,但是应该多数都是在序列上的吧
#include <cstdio>
#include <cstring>
const int mod = 998244353;
int dp[305][305][305], s[305][305][305];
inline int min(const int x, const int y) {return x < y ? x : y;}
int main() {
int n, m;
scanf("%d%d", &n, &m);
memset(s, 0x3f, sizeof s);
for (int i = 1, l, r; i <= m; ++ i) {
scanf("%d%d", &l, &r);
for (int j = l; j <= r; ++ j) s[l][r][j] = min(s[l][r][j], l);
}
for (register int i = n; i; -- i)
for (register int j = i + 1; j <= n; ++ j)
for (register int k = i; k <= j; ++ k)
s[i][j][k] = min(j + 1, min(s[i][j][k], min(s[i][j - 1][k], s[i + 1][j][k])));
for (int i = 1; i <= n; ++ i) dp[i][i][i] = 1;
for (int i = 1; i <= n + 1; ++ i)
for (int j = 1; j <= n + 1; ++ j) dp[i][i - 1][j] = 1;
for (register int l = n; l; -- l)
for (register int r = l + 1; r <= n; ++ r)
for (register int k = r; k >= l; -- k)
dp[l][r][k] = (dp[l][r][k + 1] + 1ll * dp[l][k - 1][s[l][r][k]] * dp[k + 1][r][k + 1]) % mod;
printf("%d", dp[1][n][1]);
return 0;
}