原题链接
题意:
有 n 栈灯 ,每栈灯在 [ l , r] 时间段内是开着的 ,现在需要选择 k 栈灯 ,使他们能在同一时间内打开 ,问有多少种选择方法 。
题解:
先离散化,差分,算出每个点覆盖的区间数量s[i] ,
(s[左端点]++,s[右端点+1]- -)然后对s求前缀和)
并记录这个点含的左端点数量bb[i]。对于每一个点,求组合数的时候,每个方案必须包含至少一个左端点,这样就不会重复。即
C
(
s
u
m
[
i
]
,
k
)
−
C
(
s
u
m
[
i
]
−
b
b
[
i
]
,
k
)
C(sum[i],k)-C(sum[i]-bb[i],k)
C(sum[i],k)−C(sum[i]−bb[i],k)
(i 全部点的数量任取k个,减掉全部都不取左端点的数量)
组合数可以用定义求解。
c
a
b
=
a
!
b
!
∗
(
a
−
b
)
!
c_a^b=\frac{a!}{b!*(a-b)!}
cab=b!∗(a−b)!a!
求分母的时候要用逆元。
AC代码 O ( n l o g n ) O(nlogn) O(nlogn):
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,mod=998244353;
//N开大一点,不然可能会越界。因为每个点都有一个l和 r
typedef long long ll;
ll n,m,sum[N],val,bb[N];//注意开ll
//sum某个点被覆盖的数量,bb这个点是某区间左端点数量
ll in[N],inv[N];//in表示阶乘,inv表示逆元
vector<int>alls;
struct node
{
int l,r;
}a[N];
ll qp(ll a,ll b)
{
ll res=1;
while(b)
{
if(b&1)res=res*a%mod;
b>>=1;
a=a*a%mod;
}
return res%mod;
}
int find(int x)
{
return lower_bound(alls.begin(),alls.end(),x)-alls.begin();
//找到离散化后对应的下标
}
void ini()
{
in[0]=inv[0]=1;
for(int i=1;i<N;i++)in[i]=in[i-1]*i%mod;
inv[N-1]=qp(in[N-1],mod-2);
for(int i=N-2;i>=1;i--)inv[i]=inv[i+1]*(i+1)%mod;
}
ll c(ll a,ll b)
{
if(a<b)return 0;
return (in[a]*inv[b])%mod*inv[a-b]%mod;
}
int main()
{
cin>>n>>m;
ini();
for(int i=0;i<n;i++)
{
scanf("%d%d",&a[i].l,&a[i].r);
alls.push_back(a[i].l);
alls.push_back(a[i].r+1);
}
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
for(int i=0;i<n;i++)
{
int x=find(a[i].l),y=find(a[i].r+1);
bb[x]++;
sum[x]++,sum[y]--;
}
for(int i=1;i<alls.size();i++)sum[i]+=sum[i-1];//每个端点覆盖数量
ll ans=0;
for(int i=0;i<alls.size();i++)
{
if(sum[i]<m)continue;
ans=(ans+c(sum[i],m)-c(sum[i]-bb[i],m)+mod)%mod;//对于某一个点,必须选到左端点是这个点的组合数(每个组合数里面取,至少有一个是左端点)
}
cout<<ans;
return 0;
}