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; }