P3643 & CF1295F 题解
题意:
给定 \(n\) 个区间 \([l_i, r_i]\),表示 \(a_i \in [l_i, r_i]\),\(a_i\) 可以不选,要求选了的 \(a_i\) 单调递增,求方案数。
\(n \le 500, 1 \le l_i \le r_i \le 10^9\)。
思路:
同类型题弱化版:CF1295F,每个必须选且不降。
非常有意思的技巧。
首先,总方案数是 \(\prod_{i=1}^n(r_i-l_i+1)\)。
所以我们只用求出所有单调不增的方案数即可。
最原始的思路:设 \(dp_{i,j}\) 表示 \(a_i=j\) 时前 \(i\) 项单调不增的方案数。
转移也很好想:\(dp_{i,j}=\sum_{k \ge j}dp_{i-1,k}\)。
但是 \(j\) 的范围很大,没法算。
这时候我们就要改变一下状态。
注意到 \(n \le 50\),我们发现可以把数轴分成不超过 \(2n\) 个左闭右开的小区间,使得每一对 \([l_i,r_i]\) 都可以用连续的几个这样的区间表示。
即我们存下所有的 \(l_i,r_i+1\) 然后离散化即可。
这样我们就把状态改成 \(dp_{i,j}\) 表示 \(a_i\) 在第 \(j\) 个小区间的范围内,前 \(i\) 项单调不增的方案数。
空间复杂度 \(O(n^2)\),现在考虑转移。
首先枚举所有状态是 \(O(n^2)\)。
我们枚举上一个不在 \(j\) 区间的数的位置 \(k\),以及它在那个区间,这个是 \(O(n^2)\) 的。
现在我们相当于需要求有若干个数都在 \(j\) 区间内,它们单调不增的概率。
这个问题其实就是可重集,可以用挡板法解决。
设 \(j\) 区间长度为 \(len\),则方案数为 \(C_{i-k+len-1}^{i-k}\)。
由于 \(i-k\) 范围很小,所以我们直接暴力计算,复杂度 \(O(n)\)。
所以总的时间复杂度是 \(O(n^5)\)。
当然这题也可以继续优化到 \(O(n^3)\),但 \(O(n^5)\) 也能过。
点击查看代码
#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 55;
const int mod = 998244353;
int fpow(int a, int b, int p) {
if (b == 0)
return 1;
int ans = fpow(a, b / 2, p);
ans = (1ll * ans * ans) % p;
if (b % 2 == 1)
ans = (1ll * a * ans) % p;
return ans;
}
int n;
int l[MAXN] = {0}, r[MAXN] = {0};
int seg[MAXN * 2] = {0};
int dp[MAXN][MAXN * 2] = {{0}};
int inv[MAXN] = {0};
int cmb(int m, int n) {
int ans = m;
for (int i = 2; i <= n; i++)
ans = (1ll * ans * (m - i + 1) % mod * inv[i]) % mod;
return ans;
}
map<int, int> mp;
int main() {
cin >> n;
int sum = 1;
for (int i = 1; i <= n; i++)
inv[i] = fpow(i, mod - 2, mod);
for (int i = 1; i <= n; i++) {
cin >> l[i] >> r[i];
++r[i];
sum = (1ll * sum * (r[i] - l[i])) % mod;
mp[l[i]] = mp[r[i]] = 1;
}
sum = fpow(sum, mod - 2, mod);
int cur = 0;
for (auto &i: mp)
i.second = ++cur, seg[cur] = i.first;
for (int i = 1; i <= n; i++)
l[i] = mp[l[i]], r[i] = mp[r[i]];
for (int i = 1; i <= cur; i++)
dp[0][i] = 1;
for (int i = 1; i <= n; i++)
for (int j = l[i]; j < r[i]; j++)
for (int k = i - 1; k >= 0; k--) {
if (!(l[k + 1] <= j && j < r[k + 1]))
break;
if (k == 0)
dp[i][j] = (dp[i][j] + cmb(i + (seg[j + 1] - seg[j]) - 1, i) % mod) % mod;
else {
for (int l = j + 1; l <= cur; l++)
dp[i][j] = (dp[i][j] + 1ll * dp[k][l] * cmb(i - k + (seg[j + 1] - seg[j]) - 1, i - k) % mod) % mod;
}
}
int ans = 0;
for (int i = l[n]; i < r[n]; i++)
ans = (ans + dp[n][i]) % mod;
cout << (1ll * ans * sum) % mod << endl;
return 0;
}
说回原题,现在不同在于可以不选,我们需要一个引理:再 \([0, L]\) 中选 \(n\) 个数,要求除了 \(0\) 之外都单调递增,方案数是 \(\binom{n + L}{n}\)。
可以相当于从 \(n\) 个 \(0\) 和 \([1, L]\) 这 \(n + L\) 个数中选 \(n\) 个一一映射,选第 \(i\) 个 \(0\) 表示第 \(i\) 个不选,剩下的对应 \([1,L]\) 中的选法。
然后就可以用这个结论来算,前缀和优化,时间复杂度 \(O(n^3)\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 505;
const int mod = 1e9 + 7;
int n;
int a[N] = {0}, b[N] = {0};
int inv[N] = {0};
int C[N][N] = {{0}};
void init() {//先处理逆元
inv[1] = 1;
for (int i = 2; i <= n; i++)
inv[i] = 1ll * (mod - 1) * (mod / i) % mod * inv[mod % i] % mod;//, cout << i << " " << inv[i] << " " << 1ll * i * inv[i] % mod << endl;
C[0][0] = 1;
for (int i = 1; i <= n; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
}
map<int, int> mp;
int cur = 0;
int l[N * 2] = {0}, r[N * 2] = {0};
void initSeg() {//将区间离散化成小区间
for (auto &i: mp) {
i.second = ++cur;
r[cur - 1] = l[cur] = i.first;
}
// for (int i = 1; i <= cur; i++)
// cout << l[i] << " " << r[i] << endl;
for (int i = 1; i <= n; i++)
a[i] = mp[a[i]], b[i] = mp[b[i] + 1] - 1;//, cout << i << " " << a[i] << " " << b[i] << endl;
}
int dp[N][N * 2] = {{0}};
int f[N][N * 2] = {{0}};
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i] >> b[i], mp[a[i]] = mp[b[i] + 1] = 1;
init();
initSeg();
for (int j = 0; j <= cur; j++)
f[0][j] = 1;
for (int i = 1; i <= n; i++) {
for (int j = a[i]; j <= b[i]; j++) {
int cmb = r[j] - l[j];
int L = r[j] - l[j], m = 1;
for (int x = i - 1; x >= 0; x--) {
dp[i][j] = (dp[i][j] + 1ll * cmb * f[x][j - 1] % mod) % mod;
if (a[x] <= j && j <= b[x]) {
m++;
cmb = 1ll * cmb * (L + m - 1) % mod * inv[m] % mod;
}
}
}
for (int j = 1; j <= cur; j++)
f[i][j] = (f[i][j - 1] + dp[i][j]) % mod;
}
/* for (int i = 1; i <= n; i++)
for (int j = 1; j <= cur; j++)
cout << i << " " << j << " " << dp[i][j] << endl;*/
int ans = 0;
for (int i = 1; i <= n; i++)
ans = (ans + f[i][cur]) % mod;
cout << ans << endl;
return 0;
}