题解 划艇
于是我开始补n年前的题了
好题,见到想不到,想到不敢写,敢写调不对
好题!
首先可以想到一个简单的DP,令\(dp[i][j]\)为前i所学校艇数不超过j时方案数
然而发现\(j \in 1e9\)而且还没有部分分,那怎么处理呢?
可以想到离散化,然而离散化后这题好像完全没法转移
于是重点来了:在离散化后的值域上进行方案数DP
这题就当例题吧
我们把权值离散化之后会出现许多个小区间
这里有个很重要的细节:离散化权值的时候要将区间写成\([a_i, b_i+1)\)的形式再离散化,而不能直接把\(a_i, b_i\)扔进去离散化
原因是个小边界问题:在枚举区间时,我们枚举的实际上是区间\([uni_i, uni_{i+1})\)
而当出现一个区间实际上是\([uni_i, uni_i]\)时,我们这样枚举就会漏掉它
所以可以把\([a_i, b_i+1)\)扔进去离散化
然后考虑如何转移
发现当我们转移到学校\(i\),区间\(j\)时,在i前面的所有学校\(p\)只有(原范围)能取到\(j\)和不能取到\(j\)两种
而且如果取不到\(j\),在转移\(dp_{i,j}\)时学校\(p\)只能取小于\(j\)的区间,再乘上\(len\)就是方案数
如果能取到,就需要考虑在区间\(j\)内如何取了
- 引理:在区间\([0, len]\)内取\(m\)个单调上升(可以重复选0)的数的方案数是\(\binom{len+m}{m}\)
证明:就是加进去\(m\)个可选的0
所以令学校p+i~i间有\(m\)个可以取到区间j
那对于学校\(p\),如果它不取区间\(j\),它后面那部分学校的方案数就是\(\binom{len+m-1}{m}\),这里减一是因为第i所学校必须选
直接乘上\(dp_{p,k}\)即可,这里\(k\)也需要枚举
所以这一部分式子写出来是
\[dp_{i,j} = \sum\limits_{p=1}^{i-1} \binom{len+m-1}{m} \sum\limits_{k=1}^{j-1}dp_{p,k}
\]
- 组合数部分可以用一个公式 \(C_n^m = C_{n-1}^{m-1}*\frac{n}{m}\) 推出来
最后面那个求和可以前缀和优化
时间复杂度\(O(n^3)\)
具体实现上细节非常多
Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 550
#define ll long long
#define int long long
char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline ll read() {
ll ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
int n;
ll a[N], b[N], uni[N<<1], usize, dp[N], inv[N];
const ll mod=1e9+7;
signed main()
{
n=read();
for (int i=1; i<=n; ++i) a[i]=read(), b[i]=read()+1, uni[++usize]=a[i], uni[++usize]=b[i];
sort(uni+1, uni+usize+1);
usize=unique(uni+1, uni+usize+1)-uni-1;
//cout<<"usize: "<<usize<<endl;
//cout<<"uni: "; for (int i=1; i<=usize; ++i) cout<<uni[i]<<' '; cout<<endl;
for (int i=1; i<=n; ++i) a[i]=lower_bound(uni+1, uni+usize+1, a[i])-uni;
for (int i=1; i<=n; ++i) b[i]=lower_bound(uni+1, uni+usize+1, b[i])-uni;
inv[0]=inv[1]=1;
for (int i=2; i<N; ++i) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
dp[0]=1;
for (int j=1; j<usize; ++j) {
//cout<<"now j="<<j<<" and uni="<<uni[j]<<' '<<uni[j+1]<<endl;
ll len=(uni[j+1]-uni[j])%mod, c, m;
for (int i=n; i; --i) if (a[i]<=j && b[i]>=j+1) {
//cout<<"now i="<<i<<endl;
c=len, m=1;
for (int p=i-1; ~p; --p) {
//cout<<"now p="<<p<<endl;
//printf("dp[%d][%d]=(%lld+%lld*dp[%d][%d](%lld) = %lld\n", i, j, dp[i][j], c, p, j-1, dp[p][j-1], dp[i][j]+c*dp[p][j-1]);
dp[i]=(dp[i]+c*dp[p]%mod)%mod;
//cout<<"cmp: "<<a[p]<<' '<<b[p]<<' '<<uni[j]<<' '<<uni[j+1]<<endl;
if (a[p]<=j && b[p]>=j+1) {++m; c=c*(len+m-1)%mod*inv[m]%mod; /*cout<<"so now C="<<c<<endl;*/}
}
#if 0
cout<<"sum: "<<dp[i][j]<<' '<<dp[i][j-1]<<endl;
dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
cout<<"add: "<<dp[i][j]<<endl;
#endif
}
//for (int i=0; i<=n; ++i) dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
//cout<<endl;
}
ll ans=0;
for (int i=1; i<=n; ++i) ans=(ans+dp[i])%mod;
printf("%lld\n", ans);
return 0;
}