P3643 & CF1295F 题解

题意:

给定 n 个区间 [li,ri],表示 ai[li,ri]ai 可以不选,要求选了的 ai 单调递增,求方案数。

n500,1liri109

思路:

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

非常有意思的技巧。

首先,总方案数是 i=1n(rili+1)

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

最原始的思路:设 dpi,j 表示 ai=j 时前 i 项单调不增的方案数。

转移也很好想:dpi,j=kjdpi1,k

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

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

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

即我们存下所有的 li,ri+1 然后离散化即可。

这样我们就把状态改成 dpi,j 表示 ai 在第 j 个小区间的范围内,前 i 项单调不增的方案数。

空间复杂度 O(n2),现在考虑转移。

首先枚举所有状态是 O(n2)

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

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

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

j 区间长度为 len,则方案数为 Cik+len1ik

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

所以总的时间复杂度是 O(n5)

当然这题也可以继续优化到 O(n3),但 O(n5) 也能过。

点击查看代码
#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 之外都单调递增,方案数是 (n+Ln)

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

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

点击查看代码
#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 @   rlc202204  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示