BZOJ 4584 【APIO2016】 赛艇

题目链接:赛艇

  讲道理好好的Boat为啥要翻译成赛艇呢……题面中不也是划艇么……

  这道题考虑一下dp。由于划艇数量过于庞大,所以肯定不能直接记录到dp状态中。所以一个想法就是把数量离散化,然后把每个学校的数量在哪一段内记录下来。也就是说\(f_{i,j,k}\)表示前\(i\)个学校,第\(i\)所学校派出的划艇数量在区间\(j\)内,并且区间\(j\)内共有\(k\)个学校的方案数。然后分类讨论一下转移:

  当\(k\ne 1\)时,有:

\begin{aligned}
f_{i,j,k} &=\frac{C(len_j,k)}{C(len_j,k-1)}\sum_{i'=0}^{i-1}f_{i',j,k-1}  \\
&=\frac{len_j-k+1}{k}\sum_{i'=0}^{i-1}f_{i',j,k-1}
\end{aligned}

  当\(k=1\)时,有:

\begin{aligned}
f_{i,j,k}=\sum_{i'=0}^{i-1}\sum_{j'=0}^{j-1}\sum_{k'=0}^{i'}f_{i',j',k'}
\end{aligned}

  于是前缀和优化即可。可以发现记录两个前缀和之后\(f\)数组没必要记录了。时间复杂度\(O(n^3)\),空间复杂度\(O(n^2)\)。

  下面贴代码(常数略大):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define N 510
#define mod 1000000007

using namespace std;
typedef long long llg;

int le[N],ri[N],n,ni[N],d[N<<1],ld;
llg s1[N][N<<1],s2[N<<1][N];

void gi(int &x){x=lower_bound(d+1,d+ld+1,x)-d;}
int mi(llg a,int b){
	llg s=1;
	while(b){
		if(b&1) s=s*a%mod;
		a=a*a%mod; b>>=1;
	}
	return s;
}

int main(){
	File("a");
	scanf("%d",&n); ni[0]=1;
	for(int i=1;i<=n;i++){
		scanf("%d %d",&le[i],&ri[i]);
		d[++ld]=le[i],d[++ld]=++ri[i];
		ni[i]=mi(i,mod-2);
	}
	sort(d+1,d+ld+1); ld=unique(d+1,d+ld+1)-d-1;
	for(int i=1;i<=n;i++) gi(le[i]),gi(ri[i]);
	for(int i=0;i<=ld;i++) s1[0][i]=1;
	for(int i=1,x,y;i<=n;i++){
		for(int j=ri[i]-1;x=d[j+1]-d[j],j>=le[i];j--){
			for(int k=min(x,i);k>1;k--){
				y=s2[j][k-1]*ni[k]%mod*(x-k+1)%mod;
				(s1[i][j]+=y)%=mod,(s2[j][k]+=y)%=mod;
			}
			y=s1[i-1][j-1]*x%mod;
			(s1[i][j]+=y)%=mod,(s2[j][1]+=y)%=mod;
		}
		s1[i][0]=1;
		for(int j=1;j<=ld;j++){
			s1[i][j]+=s1[i-1][j]+s1[i][j-1];
			(s1[i][j]-=s1[i-1][j-1])%=mod;
			if(s1[i][j]<0) s1[i][j]+=mod;
		}
	}
	printf("%lld",(s1[n][ld]+mod-1)%mod);
	return 0;
}
posted @ 2017-05-04 19:35 lcf2000 阅读(...) 评论(...) 编辑 收藏