挺神奇的一个做法,貌似叫做值域分段?
首先考虑一个简单的暴力做法,令 (dp_{i, j}) 为考虑完前 (i) 个学校,当前派出的最大划艇数量为 (j) 的方案。
那么按照第 (i) 个学校派不派划艇转移就会有:
前缀和优化一下就可以做到 (O(nmax(a_i, b_i))) 了,但因为值域过大,这个做法显然是不行的。
那么一个直接的想法就是通过改写状态来解决这个问题,但是很难发现一个不依赖于值域的状态,那么就只能转化这个问题了。
既然值域过大,平常做其他的题目常用的办法就是使用离散化,何不在本题上也尝试一番呢?
离散化过后你会发现,值域会被至多划分成 (2n - 1) 个简单区间(不包含其他端点)。
并且每一个简单区间和原先的每所学校的划艇限制 ([a_i, b_i]) 只能是包含或者是相离关系。
这意味着如果一所学校选择在某个区间内的一个权值放置划艇,可行的方案数实质上已经与这个学校无关了,只于放置的这个区间大小有关了。
那么就可以考虑一个基于区间放置的一个 (dp)。
具体地,对于学校 (i),考虑选择区间 (j) 中的一个权值放置划艇。
因为题目的要求,那么 (i) 之前的所有学校能放的区间一定不大于 (j),这样就形成了拓扑序,可以考虑 (dp) 了。
不难发现可以令 (dp_{i, j, k}) 表示当前考虑到第 (i) 所学校,当前第 (j) 个区间中放置了 (k) 个学校的划艇的方案。
同样根据当前学校是否放置划艇转移即可:
对于这条转移,直接记录前缀和即可。
最后,考虑如何在当前区间最后再添加一个数。
可以发现,从一个长度为 (L) 的区间中选出 (n) 个数形成递增序列的方案就相当于选出 (n) 个互不相同的数然后按顺序排好,即:(dbinom{L}{n})。
添加一个数的方案可以考虑先把之前的方案先除以 (dbinom{L}{n - 1}) 再乘 (dbinom{L}{n}) 即可。
添加一个数的方案即 (dfrac{inom{L}{n}}{inom{L}{n - 1}} = dfrac{L - n + 1}{n})
因此有转移:
最终实现的时候简单的区间要定义为左闭右开,这样就不会出现端点处选择记重的情况了,同时,需要滚动数组。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; ++i)
const int N = 500 + 5;
const int Mod = 1e9 + 7;
int n, tot, cur, a[N], b[N], inv[N], d[N * 2], S[2][N * 2], dp[2][N * 2][N];
int read() {
char c; int x = 0, f = 1;
c = getchar();
while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int Inc(int a, int b) { return (a += b) >= Mod ? a - Mod : a;}
int Dec(int a, int b) { return (a -= b) < 0 ? a + Mod : a;}
int Mul(int a, int b) { return 1ll * a * b % Mod;}
int fpow(int a, int b) { int ans = 1; for (; b; a = Mul(a, a), b >>= 1) if(b & 1) ans = Mul(ans, a); return ans;}
int main() {
n = read();
rep(i, 1, n) inv[i] = fpow(i, Mod - 2);
rep(i, 1, n) a[i] = d[++tot] = read(), b[i] = d[++tot] = read() + 1;
sort(d + 1, d + tot + 1), tot = unique(d + 1, d + tot + 1) - d - 1;
cur = dp[0][0][0] = 1;
rep(i, 0, tot - 1) S[0][i] = 1;
rep(i, 1, n) {
rep(j, 0, tot - 1) rep(k, 0, i) dp[cur][j][k] = 0;
rep(j, 0, tot - 1) rep(k, 0, i) dp[cur][j][k] = Inc(dp[cur][j][k], dp[cur ^ 1][j][k]);
rep(j, 1, tot - 1) if(a[i] <= d[j] && b[i] >= d[j + 1]) {
dp[cur][j][1] = Inc(dp[cur][j][1], Mul(S[cur ^ 1][j - 1], d[j + 1] - d[j]));
int L = min(i, d[j + 1] - d[j]);
rep(k, 2, L) dp[cur][j][k] = Inc(dp[cur][j][k], Mul(dp[cur ^ 1][j][k - 1], Mul(d[j + 1] - d[j] - k + 1, inv[k])));
}
rep(j, 0, tot - 1) S[cur][j] = 0;
rep(j, 0, tot - 1) {
if(j) S[cur][j] = S[cur][j - 1];
rep(k, 0, i) S[cur][j] = Inc(S[cur][j], dp[cur][j][k]);
}
cur ^= 1;
}
printf("%d", Dec(S[cur ^ 1][tot - 1], 1));
return 0;
}
值得一提的是,某些能运用在其他领域的做法,可能在这个领域的某些特殊问题上也会适用,不要忽略这个方面的思考。