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;
}