[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;
}
posted @ 2019-05-08 15:36  comld  阅读(275)  评论(2编辑  收藏  举报