F. Good Contest
input
3
1 2
1 2
1 2
output
499122177
input
2
42 1337
13 420
output
578894053
input
2
1 1
0 0
output
1
input
2
1 1
1 1
output
1
Note
The real answer in the first test is 12.
题目大意
给你n个题目,每个题目给出最少过题人数和最多过题人数,问你从第一道题开始过题数为非递增序列的概率为多少。
解题思路
根据题目我们可知 n 很小,但是 n 的每一个区间很大,因此我们需要进行离散化处理,之后我们用dp[i][j] 表示第i道题,在过 j 以内个人的方案有几种,然后一直递归下去就可以了,这时候的转移方程为
在第 i 道题以前的题,过题人数都比 j 大,那么只要dp[k][j+1] *和这个组合公式就行了;这个公式代表的是n个球,每个球可以取走多次,问你去K次,一共有多少种取法,答案是 C(n+k-1,k),用到这里表示的意义是,离散化的时候每个相邻的数之间是有多个的,在前前面第q个数,这是他可以从这个长度里面多加k个值,这k个值要构成非递增序列,和这个问题很相似。似乎说了一大堆废话,但是希望大家手动模拟下就会了解点了。
这里推荐一篇大佬的博客,感觉写的也挺好
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
vector<ll>ve;
ll l[110],r[110];
ll d[55][110];
const ll mod=998244353;
ll qoww(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
//求组合数
ll zu_he(ll n,ll m)
{
if(m>n) return 0;
ll nn=1;ll mm=1;
for(ll i=1;i<=m;i++) mm=(mm*i)%mod;
for(ll j=n;j>=n-m+1;j--) nn=(nn*j)%mod;
return nn*qoww(mm,mod-2)%mod;
}
int main()
{
int n;
cin>>n;
ve.push_back(-1);
for(int i=1;i<=n;i++)
{
cin>>l[i]>>r[i];
ve.push_back(l[i]);
ve.push_back(r[i]+1);
}
// 离散化处理
sort(ve.begin(),ve.end());
ve.erase(unique(ve.begin(),ve.end()),ve.end());
for(int i=1;i<=n;i++)
{
l[i]=lower_bound(ve.begin(),ve.end(),l[i])-ve.begin();
r[i]=lower_bound(ve.begin(),ve.end(),r[i]+1)-ve.begin()-1;
}
for(int i=0;i<ve.size();i++) d[0][i]=1;//初始化第一行
for(int i=1;i<=n;i++)
{
for(int j=l[i];j<=r[i];j++)//区间的长度
{
// 这个 for 循环是代码的核心,不懂的话多看几遍并且模拟一遍
for(int k=i-1;k>=0;k--)//每次都和上一次进行计算,然后更新。
{
d[i][j]+=d[k][j+1]*zu_he(ve[j+1]-ve[j]+i-k-1,i-k)%mod;
//这列涉及一个组合数学 n个球,取出 k 个,每个可以取多次。
//问你有几中取法,就是 C(n+k-1,k);
d[i][j]%=mod;
if(l[k]>j||r[k]<j)break;
}
}
for(int j=ve.size()-1;j>=0;j--) {// 这里倒着加因为后面的数越小方案数越多嘛
d[i][j]=(d[i][j]+d[i][j+1])%mod;
}
}
ll ans=d[n][0];
//最后除以分母
for(int i=1;i<=n;i++) ans=(ans*qoww(ve[r[i]+1]-ve[l[i]],mod-2))%mod;
cout<<ans<<"\n";
return 0;
}