LOJ 2567: 洛谷 P3643: bzoj 4584: 「APIO2016」划艇

题目传送门:LOJ #2567

题意简述:

\(n\) 个位置,第 \(i\) 个位置可以填在 \([a_i,b_i]\)\(1\le a_i\le b_i\le 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']\) 转移,其中 \(0\le k<i\)\(1\le j'<j\)

转移的具体形式是:假设位置 \((k,i]\) 中一共有 \(pos\) 个位置(包括 \(i\) 本身)包含段 \(j\),而段 \(j\) 的长度为 \(len\),则 \(\mathrm{f}[k][j']\) 贡献 \(\displaystyle\sum_{x=1}^{pos}\binom{pos-1}{x-1}\binom{len}{x}\) 倍给 \(\mathrm{f}[i][j]\)。这是因为我们枚举 \(pos\) 个位置中填了 \(x\) 个位置,但是第 \(i\) 个位置必须填,所以乘上 \(\displaystyle\binom{pos-1}{x-1}\),然后 \(len\) 个可行数中选出 \(x\) 个从小到大依次填入,所以再乘上 \(\displaystyle\binom{len}{x}\)。化简一下系数:\(\displaystyle\sum_{x=1}^{pos}\binom{pos-1}{x-1}\binom{len}{x}=\sum_{x}\binom{len}{x}\binom{pos-1}{pos-x}=\binom{len+pos-1}{pos}\)。式子可以这样理解:枚举 \(x\),从 \(len\) 个数中选出 \(x\) 个数,再从 \(pos-1\) 个数中选出 \(pos-x\) 个数,这与直接从 \(len+pos-1\) 个数中选出 \(pos\) 个数等价。

那么我们有 \(\displaystyle\mathrm{f}[i][j]=\sum_{k=0}^{i-1}\binom{len+pos-1}{pos}\sum_{j'=0}^{j-1}\mathrm{f}[k][j']\)。边界是 \(\mathrm{f}[0][0]=1\)\(\mathrm{f}[0][j]=0\)\(1\le j\))和 \(\mathrm{f}[i][0]=0\)\(1\le 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\n", Ans);
	return 0;
}
posted @ 2019-03-09 18:22  粉兔  阅读(379)  评论(0编辑  收藏  举报