P3643 & CF1295F 题解

题意:

给定 \(n\) 个区间 \([l_i, r_i]\),表示 \(a_i \in [l_i, r_i]\)\(a_i\) 可以不选,要求选了的 \(a_i\) 单调递增,求方案数。

\(n \le 500, 1 \le l_i \le r_i \le 10^9\)

思路:

同类型题弱化版:CF1295F,每个必须选且不降。

非常有意思的技巧。

首先,总方案数是 \(\prod_{i=1}^n(r_i-l_i+1)\)

所以我们只用求出所有单调不增的方案数即可。

最原始的思路:设 \(dp_{i,j}\) 表示 \(a_i=j\) 时前 \(i\) 项单调不增的方案数。

转移也很好想:\(dp_{i,j}=\sum_{k \ge j}dp_{i-1,k}\)

但是 \(j\) 的范围很大,没法算。

这时候我们就要改变一下状态。

注意到 \(n \le 50\),我们发现可以把数轴分成不超过 \(2n\) 个左闭右开的小区间,使得每一对 \([l_i,r_i]\) 都可以用连续的几个这样的区间表示。

即我们存下所有的 \(l_i,r_i+1\) 然后离散化即可。

这样我们就把状态改成 \(dp_{i,j}\) 表示 \(a_i\) 在第 \(j\) 个小区间的范围内,前 \(i\) 项单调不增的方案数。

空间复杂度 \(O(n^2)\),现在考虑转移。

首先枚举所有状态是 \(O(n^2)\)

我们枚举上一个不在 \(j\) 区间的数的位置 \(k\),以及它在那个区间,这个是 \(O(n^2)\) 的。

现在我们相当于需要求有若干个数都在 \(j\) 区间内,它们单调不增的概率。

这个问题其实就是可重集,可以用挡板法解决。

\(j\) 区间长度为 \(len\),则方案数为 \(C_{i-k+len-1}^{i-k}\)

由于 \(i-k\) 范围很小,所以我们直接暴力计算,复杂度 \(O(n)\)

所以总的时间复杂度是 \(O(n^5)\)

当然这题也可以继续优化到 \(O(n^3)\),但 \(O(n^5)\) 也能过。

点击查看代码
#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 55;
const int mod = 998244353;
int fpow(int a, int b, int p) {
	if (b == 0)
		return 1;
	int ans = fpow(a, b / 2, p);
	ans = (1ll * ans * ans) % p;
	if (b % 2 == 1)
		ans = (1ll * a * ans) % p;
	return ans;
}
int n;
int l[MAXN] = {0}, r[MAXN] = {0}; 
int seg[MAXN * 2] = {0};
int dp[MAXN][MAXN * 2] = {{0}};
int inv[MAXN] = {0};
int cmb(int m, int n) {
	int ans = m;
	for (int i = 2; i <= n; i++)
		ans = (1ll * ans * (m - i + 1) % mod * inv[i]) % mod;
	return ans;
}
map<int, int> mp;
int main() {
	cin >> n;
	int sum = 1;
	for (int i = 1; i <= n; i++)
		inv[i] = fpow(i, mod - 2, mod);
	for (int i = 1; i <= n; i++) {
		cin >> l[i] >> r[i];
		++r[i];
		sum = (1ll * sum * (r[i] - l[i])) % mod;
		mp[l[i]] = mp[r[i]] = 1;
	}
	sum = fpow(sum, mod - 2, mod);
	int cur = 0;
	for (auto &i: mp)
		i.second = ++cur, seg[cur] = i.first;
	for (int i = 1; i <= n; i++) 
		l[i] = mp[l[i]], r[i] = mp[r[i]];
	for (int i = 1; i <= cur; i++)	
		dp[0][i] = 1;
	for (int i = 1; i <= n; i++) 
		for (int j = l[i]; j < r[i]; j++) 
			for (int k = i - 1; k >= 0; k--) {
				if (!(l[k + 1] <= j && j < r[k + 1]))
					break;
				if (k == 0)
					dp[i][j] = (dp[i][j] + cmb(i + (seg[j + 1] - seg[j]) - 1, i) % mod) % mod;
				else {
					for (int l = j + 1; l <= cur; l++) 
						dp[i][j] = (dp[i][j] + 1ll * dp[k][l] * cmb(i - k + (seg[j + 1] - seg[j]) - 1, i - k) % mod) % mod;
				}
				
			}
	int ans = 0;
	for (int i = l[n]; i < r[n]; i++)
		ans = (ans + dp[n][i]) % mod;
	cout << (1ll * ans * sum) % mod << endl;
	return 0;
} 

说回原题,现在不同在于可以不选,我们需要一个引理:再 \([0, L]\) 中选 \(n\) 个数,要求除了 \(0\) 之外都单调递增,方案数是 \(\binom{n + L}{n}\)

可以相当于从 \(n\)\(0\)\([1, L]\)\(n + L\) 个数中选 \(n\) 个一一映射,选第 \(i\)\(0\) 表示第 \(i\) 个不选,剩下的对应 \([1,L]\) 中的选法。

然后就可以用这个结论来算,前缀和优化,时间复杂度 \(O(n^3)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <map> 
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 505;
const int mod = 1e9 + 7;

int n;
int a[N] = {0}, b[N] = {0};

int inv[N] = {0};
int C[N][N] = {{0}};
void init() {//先处理逆元 
	inv[1] = 1;
	for (int i = 2; i <= n; i++)
		inv[i] = 1ll * (mod - 1) * (mod / i) % mod * inv[mod % i] % mod;//, cout << i << " " << inv[i] << " " << 1ll * i * inv[i] % mod << endl;
	C[0][0] = 1;
	for (int i = 1; i <= n; i++) {
		C[i][0] = 1;
		for (int j = 1; j <= i; j++)
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
	}
}

map<int, int> mp;
int cur = 0;
int l[N * 2] = {0}, r[N * 2] = {0};

void initSeg() {//将区间离散化成小区间 
	for (auto &i: mp) {
		i.second = ++cur;
		r[cur - 1] = l[cur] = i.first;
	}
//	for (int i = 1; i <= cur; i++)
//		cout << l[i] << " " << r[i] << endl;
	for (int i = 1; i <= n; i++)
		a[i] = mp[a[i]], b[i] = mp[b[i] + 1] - 1;//, cout << i << " " << a[i] << " " << b[i] << endl;
}

int dp[N][N * 2] = {{0}}; 
int f[N][N * 2] = {{0}};

int main() {
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i] >> b[i], mp[a[i]] = mp[b[i] + 1] = 1;
	init();
	initSeg();
	for (int j = 0; j <= cur; j++)
		f[0][j] = 1;
	for (int i = 1; i <= n; i++) {
		for (int j = a[i]; j <= b[i]; j++) {
			int cmb = r[j] - l[j];
			int L = r[j] - l[j], m = 1; 
			for (int x = i - 1; x >= 0; x--) {
				dp[i][j] = (dp[i][j] + 1ll * cmb * f[x][j - 1] % mod) % mod; 
				if (a[x] <= j && j <= b[x]) {
					m++;
					cmb = 1ll * cmb * (L + m - 1) % mod * inv[m] % mod;
				}
			}
		}  
		for (int j = 1; j <= cur; j++)
			f[i][j] = (f[i][j - 1] + dp[i][j]) % mod;
	}
	
/*	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= cur; j++)
			cout << i << " " << j << " " << dp[i][j] << endl;*/
	int ans = 0;
	for (int i = 1; i <= n; i++)
		ans = (ans + f[i][cur]) % mod; 
	cout << ans << endl;
	return 0;
} 
`
posted @ 2024-02-28 21:44  rlc202204  阅读(9)  评论(0编辑  收藏  举报