[数学记录]CF1295F Good Contest
command_block
2020-06-05 12:03:22
**题意** : 有一个数组 $A$ ,满足 $A[i]$ 的值在 $[L[i],R[i]]$ 的整数中随机。
问最终 $A$ 不增的概率,对 $998244353$ 取模。
$n\leq 50$ ,时限$\texttt{3s}$。
------------
求出方案数后除以 $\prod(R_i-L_i+1)$ 即为概率。
首先不难想到一个 $O(nR)$ 的`DP`。
设 $f[i][j]$ 为第 $i$ 个数为 $j$ ,且前面的情况已确定的方案数。
有转移 $f[i][j]=\sum\limits_{k=1}^{j}f[i-1][k]$ ,可以前缀和优化。
但是本题 $R$ 的范围很大,不可能完整记录在状态中。
注意到我们的限制只涉及到相对大小关系,考虑离散化,每次观察“段”的贡献(区间左闭右开)。
改设 $f[i][j]$ 为第 $i$ 个数在第 $j$ 个区间内,且下一个数不在 $j$ 区间内的方案数。
转移时,需要得知前面有多少个学校已在 $j$ 区间内。
一种方法是,直接枚举上一个在 $j$ 区间之前的位置,然后把一个区间内的位置都安排到 $j$ 区间内。
在 $[1,c]$ 内选择 $k$ 个不增的数,可以使用插板法,方案数为 $\dbinom{c+k-1}{k}$。
( 这里 $c$ 很大而 $k$ 较小,需要使用递推求组合数 )
可以写出转移方程 :
设 $len[i]$ 为第 $i$ 段的长度。
$$f[i][j]=\sum\limits_{k=0}^{i-1}\sum\limits_{t=1}^{j-1}\dbinom{len+i-k}{i-k}f[k][t]$$
$$=\sum\limits_{k=0}^{i-1}\dbinom{len+i-k}{i-k}\sum\limits_{t=1}^{j-1}f[k][t]$$
前缀和优化即可做到 $O(n^3)$。(本题代码没有使用这个优化)
```cpp
#include<algorithm>
#include<cstdio>
#define ll long long
#define MaxN 55
using namespace std;
const int mod=998244353;
ll powM(ll a,int t=mod-2){
ll ret=1;
while(t){
if (t&1)ret=ret*a%mod;
a=a*a%mod;t>>=1;
}return ret;
}
ll inv[MaxN];
void Init(int n){
inv[1]=1;
for (int i=2;i<=n;i++)
inv[i]=inv[mod%i]*(mod-mod/i)%mod;
}
ll f[MaxN][MaxN*2],C[MaxN];
int n,m,l[MaxN],r[MaxN],x[MaxN*2];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d%d",&l[i],&r[i]);
l[i]=mod-l[i];r[i]=mod-r[i];
swap(l[i],r[i]);
x[i*2-1]=--l[i];x[i*2]=r[i];
}sort(x+1,x+(m=n+n)+1);
Init(n);
f[0][1]=1;l[0]=0;r[0]=mod;
for (int i=1;i<=n;i++){
for (int j=2;j<=m;j++)
if (l[i]<x[j]&&x[j]<=r[i]){
int len=x[j]-x[j-1];
if (!len)continue;
C[0]=1;
for (int k=1;k<=i;k++)
C[k]=C[k-1]*(len+k-1)%mod*inv[k]%mod;
for (int k=i-1;k>=0;k--){
ll sav=0;
for (int t=1;t<j;t++)
sav+=f[k][t];
f[i][j]=(f[i][j]+sav%mod*C[i-k])%mod;
if (!(l[k]<x[j]&&x[j]<=r[k]))break;
}
}
}
ll buf=1;
for (int i=1;i<=n;i++)
buf=buf*powM(r[i]-l[i])%mod;
ll ans=0;
for (int i=2;i<=m;i++)ans+=f[n][i];
printf("%lld",ans%mod*buf%mod);
return 0;
}
```
## + P3643 [APIO2016]划艇
和上一题基本相同,只是多了一个不参加的情况,而且要求严格递增。
设 $f[i][j]$ 为第 $i$ 个数参加,且在第 $j$ 个区间内,下一个数不在第 $j$ 个区间内的方案数。
在 $[1,c]$ 内选择 $k$ 个严格递增的数,可以使用插板法,方案数为 $\dbinom{c}{k}$。
然后考虑除了末尾的位置插入 $0$ 。枚举 $0$ 的个数求和,可以得到方案数为 $\sum\limits_{i=0}^{k-1}\dbinom{k-1}{i}\dbinom{c}{k-i}$
这是范德蒙德卷积,总的方案数是 $\dbinom{c+k-1}{k}$。
```cpp
#include<algorithm>
#include<cstdio>
#define ll long long
#define MaxN 505
using namespace std;
const int mod=1000000007;
ll inv[MaxN];
void Init(int n){
inv[1]=1;
for (int i=2;i<=n;i++)
inv[i]=inv[mod%i]*(mod-mod/i)%mod;
}
ll f[MaxN][MaxN*2],g[MaxN][MaxN*2];
int n,m,l[MaxN],r[MaxN],x[MaxN*2];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d%d",&l[i],&r[i]);
x[i*2-1]=--l[i];x[i*2]=r[i];
}sort(x+1,x+n+n+1);
m=unique(x+1,x+n+n+1)-x-1;
Init(n);
for (int i=1;i<=m;i++)g[0][i]=1;
for (int i=1;i<=n;i++)
for (int j=2;j<=m;j++){
if (l[i]<x[j]&&x[j]<=r[i]){
int len=x[j]-x[j-1],o=1;
ll C=len;
for (int k=i-1;k>=0;k--){
f[i][j]=(f[i][j]+g[k][j-1]*C)%mod;
if (l[k]<x[j]&&x[j]<=r[k]){
o++;
C=C*(len+o-1)%mod*inv[o]%mod;
}
}
}g[i][j]=(g[i][j-1]+f[i][j])%mod;
}
ll ans=0;
for (int i=1;i<=n;i++)
ans+=g[i][m];
printf("%lld",ans%mod);
return 0;
}
```
### 另解
利用分段多项式函数。
设 $f_i(n)$ 表示前 $i$ 个数已经确定,末尾为 $n$ 的方案数,写出暴力 $\rm DP$ 式 :
$$f_i(x)=f_{i-1}(x)+\big[n\in[L_i,R_i]\big]\sum\limits_{k<x}f_{i-1}(k)$$
可以证明 $f_i(x)$ 是分段的多项式函数,具体做法就是一种构造证明。
也可以通过前缀和变换归纳证明,这里就不详细展开了。
对于一个分段函数,我们要支持简单加法,以及 $g(x)=\sum\limits_{k<x}f_{i-1}(k)$ 的点值前缀和计算。
不难发现点值前缀和就是离散微积分,为了方便积分,将多项式写成下降幂的形式即可。
```cpp
```