link
难度
思维难度 : 三星
代码难度 :二星
标签
dp + 离散化 + 计数
思路
考虑最最朴素的 dp , 设 f[i][j] 表示 dp 到第 i 所学校,第 i 所学校参加,最大值为 j , 那么显然有 f[i][j]=∑p=0i−1∑k=0j−1f[p][k](ai≤j≤bi) , 用前缀和优化可以做到 O(n2max(bi)) 不能接受.
考虑优化掉 max(bi) , 把 ai 和 bi 离散化 , 这样可以优化到 O(n3) , 但是又有一个问题, f[i][j] 的意义会变为 dp 到第 i 所学校,第 i 所学校参加,最大值所在区间为 j . 可以想到一种 dp.
f[i][j]=∑p=0i−1∑k=0j−1(rj−lj+1)f[p][k] ,
然而这样是不对的,因为没有考虑 p 到 j 之间有些数也可以在区间 j 中. 所以方程应该是 f[i][j]=∑p=0i−1∑k=0j−1g(lj,rj,num(p+1,i,j))f[p][k]
num(i,j,x) 表示从 i 到 j 包含区间 x 的数的数量.
g(l,r,x) 表示从区间 [0,0]+[l,r] 中取 x 个数,第 x 个数非零 , 要求所有非零数严格递增的方案数. 这显然是 (xr−l+x) , 证明如下:
枚举哪几个数选 0 , 让其它的数递增。那么方案数为
i=0∑x(ix)(x−ir−l+1)−i=0∑x−1(ix−1)(x−i−1r−l+1)减去最后一个数为0的方案=(xr−l+1+x)−(x−1r−l+x)=(xr−l+x)
所以 f[i][j]=∑p=0i−1∑k=0j−1(xr−l+x)f[p][k]
前缀和可以优化到 O(n3)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include <cstdio> #include <iostream> #include <vector> #include <algorithm> #define ll long long
using namespace std;
const int N = 3e3 + 10, mod = 1e9 + 7; int n; int C[N], a[N], b[N], tmp[N]; int f[N][N], g[N][N]; int inv[N];
int main() { scanf("%d", &n); inv[1] = 1; for (int i = 2; i <= n; ++i) { inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod; } int tot = 0; for (int i = 1; i <= n; ++i) { scanf("%d%d", &a[i], &b[i]); ++b[i]; tmp[++tot] = a[i], tmp[++tot] = b[i]; } sort(tmp + 1, tmp + tot + 1); tot = unique(tmp + 1, tmp + tot + 1) - tmp - 1; for (int i = 1; i <= n; ++i) { a[i] = lower_bound(tmp + 1, tmp + tot + 1, a[i]) - tmp; b[i] = lower_bound(tmp + 1, tmp + tot + 1, b[i]) - tmp; } C[0] = 1; for (int i = 0; i < tot; ++i) g[0][i] = 1; int ans = 0; for (int j = 1; j < tot; ++j) { int L = tmp[j + 1] - tmp[j]; for (int i = 1; i <= n; ++i) C[i] = 1ll * C[i - 1] * (L - 1 + i) % mod * inv[i] % mod; for (int i = 1; i <= n; ++i) { if (a[i] <= j && j + 1 <= b[i]) { int m = 1; for (int k = i - 1; ~k; --k) { f[i][j] = (f[i][j] + 1ll * g[k][j - 1] * C[m] % mod) % mod; if (a[k] <= j && j + 1 <= b[k]) ++m; } } g[i][j] = 0; for (int k = 1; k <= j; ++k) (g[i][j] += f[i][k]) %= mod; ans += f[i][j]; ans %= mod; } } cout << ans << endl; return 0; }
|
总结
当 dp 中涉及较大的值域时,可以考虑离散化后进行 dp ,在 dp 时要注意很多细节。如要考虑之前和现在选的数在同一区间的情况.