Loading

P3643 [APIO2016] 划艇 (线性 dp+离散化+组合数)

P3643 [APIO2016] 划艇

线性 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;
}
posted @ 2024-06-11 22:02  Fire_Raku  阅读(3)  评论(0编辑  收藏  举报