P3643/BZOJ4584 [APIO2016]划艇
题意:给出n个闭区间,从每个区间取一个整数,或者取0,要求非零数严格递增,问方案数。
发现N<=500,那么划分出的区间是一个比较小的数。
于是我们离散化区间,题目给出闭区间,转化成一个左闭右开区间。
定义f(i,j)为第i所学校最后参赛,且所选区间在第j个的方案数。
这时候前面的每个学校都分为两种,在区间内的和不在区间内的。
我们枚举最后一个不在区间内的学校p作为计数的基准点避免重复。
设m为[p+1,i]这个闭区间内在j区间内的学校个数,l为这个j区间的长度,可以发现选出学校就可以确定一种方案。
我们要确定这个方案数,即m个学校可以取j区间内还未取的数或0,其中当前的第i座学校必须取非0值的方案数,即为C(l+m-1,m)
f(i,j)=∑k∑p f(p,k)*C(l+m-1,m) [1<=k<=j-1;0<=p<=i-1],压掉j这一维,C在每一次枚举区间时进行预处理,并处理前缀和进行转移。
代码如下:
#include<iostream> #include<cstdio> #include<algorithm> #include<string> #include<cstring> #include<cmath> #include<queue> using namespace std; typedef long long ll; const ll N=5e2+10,mod=1e9+7; int n,m,ans,a[N],b[N],c[N*2],C[N],g[N],inv[N]; inline ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return x*f; } int main(){ n=read();inv[1]=1; for(int i=2;i<=n;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod; for(int i=1;i<=n;i++) c[++m]=a[i]=read(),b[i]=read(),c[++m]=b[i]+1; sort(c+1,c+1+m);m=unique(c+1,c+1+m)-(c+1); for(int i=1;i<=n;i++){ a[i]=lower_bound(c+1,c+1+m,a[i])-c; b[i]=lower_bound(c+1,c+1+m,b[i]+1)-c; } C[0]=1;g[0]=1; for(int j=1;j<=m-1;j++){ int len=c[j+1]-c[j]; for(int i=1;i<=n;i++) C[i]=1ll*C[i-1]*(len+i-1)%mod*inv[i]%mod; for(int i=n;i>=1;i--) if(a[i]<=j&&j+1<=b[i]){ int f=0,t=1,p=C[1]; for(int k=i-1;~k;k--){ f=1ll*(f+1ll*p*g[k]%mod)%mod; if(a[k]<=j&&j+1<=b[k]) p=C[++t]; } g[i]=1ll*(g[i]+1ll*f)%mod; } } for(int i=1;i<=n;i++) ans=1ll*(ans+1ll*g[i])%mod; return printf("%d\n",ans),0; }