P3643 [APIO2016] 划艇 (线性 dp+离散化+组合数)
线性 dp+离散化+组合数
一个很朴素的状态就是设 \(f_{i,j}\) 表示前 \(i\) 所学校排了 \(j\) 艘潜艇的方案数。但是 \(j\le 10^9\),过不了一点。
考虑每个学校对应数轴上一个区间,发现题目只关心大小关系,所以考虑离散化。这里离散化的端点为 \(a_i\) 和 \(b_i+1\),表示 \([a_i,b_i+1)\),这样子离散化完每个区间起始位置不需要再次增减下标,方便。
那么状态也变化为 \(f_{i,j}\) 表示考虑前 \(i\) 所学校,第 \(i\) 所学校所派划艇数量在编号 \(j\) 区间的方案数。由于前 \(i\) 所学校的区间编号一定 \(\le j\),所以枚举一段编号为 \(j\) 的后缀 \([k+1,i]\),那么前面的状态就是 \(f_{k,p}\)。
转移方程:
\[f_{i,j}=\sum_{k=0}^{i-1}\sum_{p=0}^{j-1} f_{k,p}\times val(k+1,i,j)=\sum_{k=0}^{i-1}val(k+1,i,j)\sum_{p=0}^{j-1} f_{k,p}
\]
\(\sum_{p<j} f_{k,p}\) 这段可以前缀和优化。\(val(i,j,k)\) 表示 \([i,j]\) 区间内的学校派出的划艇数量都在 \(k\) 区间的方案数(不包括不派出划艇的学校)。那么如何求出 \([k+1,i]\) 学校的方案数 \(val(k+1,i,j)\) ?首先第 \(i\) 所学校决策固定,设包含 \(j\) 区间的学校数量为 \(n\)(不包含 \(i\)),那么设贡献式子为:
\[\sum_{i=0}^nC_{n}^iC_{l}^{i+1}=\sum_{i=0}^nC_{n}^{n-i}C_{l}^{i+1}=C_{n+l}^{n+1}
\]
这里组合数太大,可以从后往前枚举动态维护这个组合数。
复杂度 \(O(n^3)\)。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 510, mod = 1e9 + 7;
i64 n, tot, ans;
struct node {
i64 l, r;
} a[N];
i64 b[N << 1], f[N][N << 1], inv[N], l[N << 1];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n;
for(int i = 1; i <= n; i++) {
std::cin >> a[i].l >> a[i].r;
b[++tot] = a[i].l, b[++tot] = ++a[i].r;
}
std::sort(b + 1, b + tot + 1);
tot = std::unique(b + 1, b + tot + 1) - b - 1;
for(int i = 1; i <= n; i++) {
a[i].l = std::lower_bound(b + 1, b + tot + 1, a[i].l) - b;
a[i].r = std::lower_bound(b + 1, b + tot + 1, a[i].r) - b;
}
tot--;
for(int i = 1; i <= tot; i++) {
l[i] = b[i + 1] - b[i];
}
inv[1] = 1;
for(int i = 2; i <= n; i++) {
inv[i] = 1LL * (mod - mod / i) * inv[mod % i] % mod;
}
for(int i = 0; i <= tot; i++) f[0][i] = 1;
for(int i = 1; i <= n; i++) {
for(int j = a[i].l, r = a[i].r; j < r; j++) {
i64 C = l[j], len = l[j], m = 1;
for(int k = i - 1; k >= 0; k--) {
f[i][j] = (f[i][j] + 1LL * f[k][j - 1] * C % mod) % mod;
if(a[k].l <= j && j < a[k].r) {
len++, m++;
C = (C * len % mod * inv[m] % mod) % mod;
}
}
}
for(int j = 2; j <= tot; j++) f[i][j] = (f[i][j] + f[i][j - 1]) % mod;
}
for(int i = 1; i <= n; i++) {
ans = (ans + f[i][tot]) % mod;
}
std::cout << ans << "\n";
return 0;
}