P3643 [APIO2016] 划艇
题意:
在首尔城中,汉江横贯东西。在汉江的北岸,从西向东星星点点地分布着 \(N\) 个划艇学校,编号依次为 \(1\) 到 \(N\)。每个学校都拥有若干艘划艇。同一所学校的所有划艇颜色相同,不同的学校的划艇颜色互不相同。颜色相同的划艇被认为是一样的。每个学校可以选择派出一些划艇参加节日的庆典,也可以选择不派出任何划艇参加。如果编号为 \(i\) 的学校选择派出划艇参加庆典,那么,派出的划艇数量可以在 \(a_i\) 至 \(b_i\) 之间任意选择(\(a_i \leq b_i\))。
值得注意的是,编号为 \(i\) 的学校如果选择派出划艇参加庆典,那么它派出的划艇数量必须大于任意一所编号小于它的学校派出的划艇数量。
输入所有学校的 \(a_i,b_i\) 的值,求出参加庆典的划艇有多少种可能的情况,必须有至少一艘划艇参加庆典。两种情况不同当且仅当有参加庆典的某种颜色的划艇数量不同。
\(N \le 500,a_{i},b_{i} \le 10^9\)。
分析:
由于值域有 \(10^9\),因此需要离散化成一个个小段。
发现 dp 的难点在于每个学校可以不用派出划艇。
不如利用将每个学校分成连续的一段,来刻画这个过程,在同一段的学校派出的潜艇颜色为同一段(也可以不派出),并钦定每一段的最后一个学校必选。
因此记 \(f_{i,j}\) 表示考虑前 \(i\) 个学校,并且第 \(i\) 个学校选颜色段数编号为 \(j\)。
转移可以枚举这一段的开始的学校 \(l\),和上一段的结尾颜色段数编号 \(k\)。
其中 \(\tbinom{m+L-1}{m}\) 表示将 \(m\) 个物品放进 \(L\) 个位置,每个物品可以不放在任何一个位置,且不能为空的方案数量。考虑构造
选第 \(i\) 个 \(0\) 表示第 \(i\) 个物品不放,选 \(x\ (x \ne 0)\) 表示第 \(x\) 个不选 \(0\) 的物品放在 \(x\) 这个位置上。
而转移中 \(\sum_{k=1}^{j-1}f_{l-1,k}\) 可以用前缀和优化。
因此时间复杂度 \(O(n^3)\)。
代码:
#include<bits/stdc++.h>
#define int long long
#define N 3010
#define mod 1000000007
using namespace std;
int Pow(int a, int n) {
if(n == 0) return 1;
if(n == 1) return a;
int x = Pow(a, n / 2);
if(n % 2 == 0) return x * x % mod;
else return x * x % mod * a % mod;
}
int inv(int x) {
return Pow(x, mod - 2);
}
int n, ans;
struct node {
int l, r;
}a[N];
int b[N], tot, c[N], len, L[N], R[N];
int h[N][N], fac[N], Inv[N], f[N][N], g[N][N];
int C(int j, int m) {
return h[j][m] * Inv[m] % mod;
}
signed main() {
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i].l >> a[i].r;
b[++tot] = a[i].l;
b[++tot] = a[i].r;
}
sort(b + 1, b + tot + 1);
for(int i = 1; i <= tot; i++)
if(i == 1 || b[i] != b[i - 1]) c[++len] = b[i];
int H = 0;
for(int i = 1; i < len; i++) {
H++;
L[H] = c[i];
R[H] = c[i];
if(c[i] + 1 <= c[i + 1] - 1) {
H++;
L[H] = c[i] + 1;
R[H] = c[i + 1] - 1;
}
}
H++;
L[H] = c[len];
R[H] = c[len];
len = H;
for(int i = 1; i <= len; i++) {
h[i][0] = 1;
int x = R[i] - L[i] + 1;
for(int j = 1; j <= n; j++) h[i][j] = h[i][j - 1] * (x + j - 1) % mod;
}
fac[0] = Inv[0] = 1;
for(int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mod;
Inv[n] = inv(fac[n]);
for(int i = n - 1; i >= 1; i--) Inv[i] = Inv[i + 1] * (i + 1) % mod;
f[0][0] = 1;
for(int i = 0; i <= len; i++) g[0][i] = 1;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= len; j++) { //枚举选什么
g[i][j] = g[i][j - 1];
if(a[i].l <= L[j] && R[j] <= a[i].r) ;
else continue;
int m = 0;
for(int ll = i; ll >= 1; ll--) {
if(a[ll].l <= L[j] && R[j] <= a[ll].r) m++;
f[i][j] += g[ll - 1][j - 1] * C(j, m) % mod;
f[i][j] %= mod;
}
ans = (ans + f[i][j]) % mod;
g[i][j] = (g[i][j] + f[i][j]) % mod;
}
}
cout << ans;
return 0;
}