CF1295F Good Contest

传送门


题意:\(a_i\)\([l_i,r_i]\)等概率取,问\(a_{1\cdots n}\)不递增的概率,对\(998244353\)取模。(\(1 \leqslant n \leqslant 50, 1 \leqslant l_i \leqslant r_i < 998244353\)


可以先求方案数,然后除以\(\prod\limits_{i=1}^n (r_i-l_i+1)\).如果直接令\(dp[i][j]\)表示第\(i\)个数选\(j\)时的方案数,显然会超时。因为\(n\)个区间最多会划分出\(2n-1\)个不相交的区间,考虑到\(n\)很小,可以从这方面入手。

先把所有区间右端点+1,处理成\(2n-1\)个左闭右开的区间。令\(dp[i][j]\)表示第\(i\)个数选择第\(j\)个区间中的某个数时合法序列的方案数。那么只要枚举他和前面多少个数选择同一个区间\(j\)即可。那么从区间\(I\)中选择\(a\)个数,使其构成不递增序列的方案数,实际上就是隔板法,而隔板法的含义就是从\(I\)中随便选出\(a\)个数的方案数,即\(\binom{|I|+a-1}{a}\),那么就有dp转移方程:

\[dp[i][j] = \sum\limits_{k < i, t > j} dp[k][t] * \binom{|I_j|+i-k-1}{i - k} \]

关于组合数,虽然\(|I_j|\)会很大,单次需要\(O(n)\)算,但是从\(k\)转移到\(k-1\)是有规律的,可以动态求。所以这样转移时间复杂度是\(O(n^4 \log n)\)的(有个快速幂的\(\log n\),应该也可以优化),再用后缀和可以优化到\(O(n^3\log n)\).

#include<bits/stdc++.h>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 55;
const ll mod = 998244353;
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

In ll ADD(ll a, ll b) {return a + b < mod ? a + b : a + b - mod;}
In ll quickpow(ll a, ll b)
{
	ll ret = 1;
	for(; b; b >>= 1, a = a * a % mod)
		if(b & 1) ret = ret * a % mod;
	return ret;
}

int n;
struct Node {int L, R;}t[maxn];
int li[maxn << 1], _n = 0;
ll dp[maxn][maxn << 1], sum[maxn][maxn << 1];

int main()
{
	n = read();
	for(int i = 1; i <= n; ++i)
	{
		int L = read(), R = read() + 1;
		li[++_n] = L, li[++_n] = R;
		t[i] = (Node){L, R}; 
	}
	sort(li + 1, li + _n + 1);
	_n = unique(li + 1, li + _n + 1) - li - 1;
	fill_n(sum[0], _n + 1, 1);
	t[0].L = 0, t[0].R = _n;
	for(int i = 1; i <= n; ++i)
	{
		t[i].L = lower_bound(li + 1, li + _n + 1, t[i].L) - li;
		t[i].R = lower_bound(li + 1, li + _n + 1, t[i].R) - li;
		for(int j = t[i].L; j < t[i].R; ++j)
		{
			ll c = li[j + 1] - li[j]; ll len = c;
			for(int k = i - 1; k >= 0; --k) 
			{ 
				if(t[k + 1].L > j || t[k + 1].R <= j) break;	//第k+1个区间可能不包含当前枚举的区间 
				dp[i][j] = ADD(dp[i][j], sum[k][j + 1] * c % mod);
				c = c * (len + i - k) % mod * quickpow(i - k + 1, mod - 2) % mod;
			} 
		}
		for(int j = _n; j; --j) sum[i][j] = ADD(sum[i][j + 1], dp[i][j]);
	}
	ll ans = sum[n][1];
	for(int i = 1; i <= n; ++i) ans = ans * quickpow(li[t[i].R] - li[t[i].L], mod - 2) % mod;
	write(ans), enter;
	return 0;
}
posted @ 2021-11-24 17:40  mrclr  阅读(45)  评论(0编辑  收藏  举报