题目传送门:LOJ #2567。
题意简述:
有 (n) 个位置,第 (i) 个位置可以填在 ([a_i,b_i]) ((1le a_ile b_ile 10^9))之间的整数,也可以填 (0)。
如果第 (i) 个位置填了非 (0) 的数,则这个数必须大于之前所有位置((1) 到 (i-1) 的位置)上的数。
至少要有一个位置填上非 (0) 的数。问最终有几种填数方案,两种填数方案不同当且仅当某个位置上填的数不同。
题解:
要求即为选出一些位置填数,并且形成严格递增序列。
发现把所有区间离散化,可以组成 (mathcal{O}(n)) 个连续段,每个连续段中的数的性质是相同的。
因为 (n) 不大(只有 (500)),考虑 DP。令 (mathrm{f}[i][j]) 表示前 (i) 个位置,第 (i) 个位置填了在段 (j) 中的数的方案数。
显然若 ([a_i,b_i]) 不包含段 (j),则 (mathrm{f}[i][j]=0)。对于其他的情况,考虑如何转移,假设上一个填了在小于 (j) 的段中的数的位置是 (k),则有 (mathrm{f}[i][j]) 从 (mathrm{f}[k][j']) 转移,其中 (0le k<i) 且 (1le j'<j)。
转移的具体形式是:假设位置 ((k,i]) 中一共有 (pos) 个位置(包括 (i) 本身)包含段 (j),而段 (j) 的长度为 (len),则 (mathrm{f}[k][j']) 贡献 (displaystylesum_{x=1}^{pos}inom{pos-1}{x-1}inom{len}{x}) 倍给 (mathrm{f}[i][j])。这是因为我们枚举 (pos) 个位置中填了 (x) 个位置,但是第 (i) 个位置必须填,所以乘上 (displaystyleinom{pos-1}{x-1}),然后 (len) 个可行数中选出 (x) 个从小到大依次填入,所以再乘上 (displaystyleinom{len}{x})。化简一下系数:(displaystylesum_{x=1}^{pos}inom{pos-1}{x-1}inom{len}{x}=sum_{x}inom{len}{x}inom{pos-1}{pos-x}=inom{len+pos-1}{pos})。式子可以这样理解:枚举 (x),从 (len) 个数中选出 (x) 个数,再从 (pos-1) 个数中选出 (pos-x) 个数,这与直接从 (len+pos-1) 个数中选出 (pos) 个数等价。
那么我们有 (displaystylemathrm{f}[i][j]=sum_{k=0}^{i-1}inom{len+pos-1}{pos}sum_{j'=0}^{j-1}mathrm{f}[k][j'])。边界是 (mathrm{f}[0][0]=1)、(mathrm{f}[0][j]=0)((1le j))和 (mathrm{f}[i][0]=0)((1le i))。
再利用前缀和优化,把第二维做前缀和,把转移变成 (mathcal{O}(n)) 的就好了,转移内的组合数可以 (mathcal{O}(n)) 预处理。
还可以滚动数组一下,空间就是 (mathcal{O}(n)) 的了,不过没必要。
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int Mod = 1000000007;
const int MN = 505;
int N, lb[MN], rb[MN], lp[MN * 2], len[MN * 2], cnt;
int Inv[MN], C[MN], f[MN], Ans;
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i)
scanf("%d%d", &lb[i], &rb[i]),
lp[++cnt] = lb[i],
lp[++cnt] = rb[i] + 1;
std::sort(lp + 1, lp + cnt + 1);
cnt = std::unique(lp + 1, lp + cnt + 1) - lp - 2;
for (int i = 1; i <= cnt; ++i) len[i] = lp[i + 1] - lp[i];
for (int i = 1; i <= N; ++i)
lb[i] = std::lower_bound(lp + 1, lp + cnt + 1, lb[i]) - lp,
rb[i] = std::lower_bound(lp + 1, lp + cnt + 1, rb[i] + 1) - lp - 1;
Inv[1] = 1, C[0] = 1, f[0] = 1;
for (int i = 2; i <= N; ++i) Inv[i] = (LL)(Mod - Mod / i) * Inv[Mod % i] % Mod;
for (int i = 1; i <= cnt; ++i) {
int l = len[i];
for (int j = 1; j <= N; ++j)
C[j] = (LL)C[j - 1] * (l + j - 1) % Mod * Inv[j] % Mod;
for (int j = N; j >= 1; --j) if (lb[j] <= i && i <= rb[j]) {
for (int k = j, a = 0; k >= 1; --k) {
if (lb[k] <= i && i <= rb[k]) ++a;
f[j] = (f[j] + (LL)f[k - 1] * C[a]) % Mod;
}
}
}
for (int i = 1; i <= N; ++i) Ans = (Ans + f[i]) % Mod;
printf("%d
", Ans);
return 0;
}