[APIO2016]划艇
https://www.luogu.org/problemnew/show/P3643
题解
我们发现这道题的值域很大,所以考虑把所有区间端点离散化。
然后我们就设一个\(dp[i][j]\)表示前i个学校,第i个学校强制选,第i个学校选在了j这个区间的方案数。
转移我们可以枚举第一个选在j这个区间的学校k。
\[dp[i][j]=blabla*\sum_{x=1}^{i-1}\sum_{y=1}^{j-1}dp[x][y]
\]
考虑前面的东西怎么算。
如果每个数都可以选或者不选的话,那么答案就是\(\binom{n+len}{n}\)。
现在我们强制头尾必须选。
我们原来是这样的表达式:
\[C_{num}^0C_{len}^0+C_{num}^1C_{len}^1+C_{num}^2C_{len}^2+...
\]
现在变成了:
\[C_{num-2}^0C_{len}^2+C_{num-2}^1C_{len}^3+C_{num-2}^2C_{len}^4+...
\]
看起来好像很复杂,但是如果考虑插板法的话,就是\(\binom{len+num-2}{num}\)。
\(O(n^3)dp\)就好了。
代码
#include<bits/stdc++.h>
#define N 509
using namespace std;
typedef long long ll;
const int mod=1000000007;
ll dp[N][N<<1],num[N<<1],l[N],r[N],ni[N],n;
inline ll rd(){
ll x=0;char c=getchar();bool f=0;
while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f?-x:x;
}
inline ll power(ll x,ll y){
ll ans=1;
while(y){if(y&1)ans=ans*x%mod;x=x*x%mod;y>>=1;}
return ans;
}
inline void MOD(ll &x){x=x>=mod?x-mod:x;}
int main(){
n=rd();
for(int i=1;i<=n;++i)ni[i]=power(i,mod-2);
for(int i=1;i<=n;++i)l[i]=rd(),r[i]=rd()+1,num[++num[0]]=l[i],num[++num[0]]=r[i];
sort(num+1,num+num[0]+1);
num[0]=unique(num+1,num+num[0]+1)-num-1;
for(int i=1;i<=n;++i){
l[i]=lower_bound(num+1,num+num[0]+1,l[i])-num;
r[i]=lower_bound(num+1,num+num[0]+1,r[i])-num;
}
for(int i=0;i<=num[0];++i)dp[0][i]=1;
for(int i=1;i<=n;++i){
dp[i][0]=1;
for(int j=l[i];j<r[i];++j){
ll len=num[j+1]-num[j];
dp[i][j]=dp[i-1][j-1]*len%mod;
ll su=len-1,cnt=1;
for(int k=i-1;k>=1;--k)if(l[k]<=j&&r[k]>j){
cnt++;
su=su*(len+cnt-2)%mod*ni[cnt]%mod;
if(!su)break;
MOD(dp[i][j]+=dp[k-1][j-1]*su%mod);
}
}
for(int j=1;j<num[0];++j)(dp[i][j]+=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mod)%=mod;
}
printf("%lld",(dp[n][num[0]-1]-1+mod)%mod);
return 0;
}