[洛谷P3634] APIO2016 划艇
问题描述
在首尔城中,汉江横贯东西。在汉江的北岸,从西向东星星点点地分布着 \(N\) 个划艇学校,编号依次为 \(1\) 到 \(N\)。每个学校都拥有若干艘划艇。同一所学校的所有划艇颜色相同,不同的学校的划艇颜色互不相同。颜色相同的划艇被认为是一样的。每个学校可以选择派出一些划艇参加节日的庆典,也可以选择不派出任何划艇参加。如果编号为 \(i\) 的学校选择派出划艇参加庆典,那么,派出的划艇数量可以在 \(a_i\) 至 \(b_i\) 之间任意选择(\(a_i \leq b_i\))。
值得注意的是,编号为 \(i\) 的学校如果选择派出划艇参加庆典,那么它派出的划艇数量必须大于任意一所编号小于它的学校派出的划艇数量。
输入所有学校的 \(a_i,b_i\) 的值,求出参加庆典的划艇有多少种可能的情况,必须有至少一艘划艇参加庆典。两种情况不同当且仅当有参加庆典的某种颜色的划艇数量不同。
输入格式
第一行包括一个整数 \(N\),表示学校的数量。
接下来 \(N\) 行,每行包括两个正整数,用来描述一所学校。其中 \(i\) 行包括的两个正整数分别表示 \(a_i,b_i(1 \leq a_i \leq b_i \leq 10^9)\)。
输出格式
输出一行,一个整数,表示所有可能的派出划艇的方案数除以 \(1,000,000,007\) 得到的余数。
样例输入
2
1 2
2 3
样例输出
7
数据范围
\(N\le 500\)
解析
首先想一个暴力DP,设 \(f_{i,j}\) 表示考虑前 \(i\) 个学校,保证第 \(i\) 个学校必选,并派出 \(j\) 个划艇的方案数。不难得到如下方程:
但是第二维是 \(10^9\) 级别。考虑把 \(a_i,b_i\) 离散化。我们的状态也要重新定义:\(f_{i,j}\) 的第二维表示第 \(i\) 个学校派出的划艇在第 \(j\) 个区间里(即第 \(j\) 到第 \(j+1\) 个点之间),记长度为 \(len\)。枚举第 \(k\) 个学校,考虑把第 \(k+1\) 到第 \(i\) 个学校全部放到第 \(j\) 个区间中。
记 \([k+1,i]\) 中能够放在区间 \(j\) 中的学校有 \(x\) 个。考虑在 \([0,len]\)中选择 \(x\) 个数,其中 0 可以重复选,但非 0 数要递增的方案数。不难得到答案为 \({len+x}\choose x\)。由于原问题中要求 \(i\) 不能为 0,所以应为 \({x+len-1}\choose x\)。由此,我们有转移方程:
\(len\) 需要从后往前转移,动态维护。转移时前缀和优化一下即可。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long
#define N 802
using namespace std;
const int mod=1000000007;
int n,i,j,k,a[N],b[N],val[N*2],cnt,f[N][N*2],g[N][N*2],c[N],inv[N];
int read()
{
char c=getchar();
int w=0;
while(c<'0'||c>'9') c=getchar();
while(c<='9'&&c>='0'){
w=w*10+c-'0';
c=getchar();
}
return w;
}
signed main()
{
n=read();
for(i=1;i<=n;i++){
a[i]=read();b[i]=read();
val[++cnt]=a[i];val[++cnt]=b[i]+1;
}
inv[1]=1;
for(i=2;i<=n;i++) inv[i]=(mod-(mod/i)*inv[mod%i]%mod)%mod;
sort(val+1,val+cnt+1);
cnt=unique(val+1,val+cnt+1)-val-1;
for(i=1;i<=n;i++){
a[i]=lower_bound(val+1,val+cnt+1,a[i])-val;
b[i]=lower_bound(val+1,val+cnt+1,b[i]+1)-val;
}
for(i=0;i<cnt;i++) g[0][i]=1;
c[0]=1;
for(j=1;j<cnt;j++){
int len=val[j+1]-val[j];
for(i=1;i<=n;i++) c[i]=c[i-1]*(len+i-1)%mod*inv[i]%mod;
for(i=1;i<=n;i++){
if(a[i]<=j&&j+1<=b[i]){
int m=1;
for(k=i-1;k>=0;k--){
f[i][j]=(f[i][j]+g[k][j-1]*c[m]%mod)%mod;
if(a[k]<=j&&j+1<=b[k]) m++;
}
}
g[i][j]=(g[i][j-1]+f[i][j])%mod;
}
}
int ans=0;
for(i=1;i<=n;i++) ans=(ans+g[i][cnt-1])%mod;
printf("%lld\n",ans);
return 0;
}