P3643-[APIO2016]划艇【dp】

正题

题目链接:https://www.luogu.com.cn/problem/P3643


题目大意

求有多少个\(n\)个数的序列\(x\)满足,\(x_i\in \{0\}\cup[a_i,b_i]\)且非\(0\)数递增。


解题思路

会发现\(a_i,b_i\)很大不能太暴力的将第二维的\(dp\)设为上一个选了的数是多少。

可以考虑离散化,会将整个数轴分成最多\(2n-1\)个区间,但是这样我们就不能确定上个数字具体在哪里了。\(n\)比较小,所以我们可以考虑一种比较合理的方法就先确定这个区间中有多少个数然后再用组合数算出这个区间的方案。

\(f_{i,j}\)表示到第\(i\)个数(并且这个数不是\(0\))在第\(j\)个区间时的方案,若从\(f_{k,l}(k<i,l<j)\)转移过来,也就是在\([k+1,i]\)的数要么在\(j\)这个区间内,要么是\(0\)。若区间\(j\)的长度为\(len\)\([k+1,i]\)中的数能选到区间\(j\)的数的个数为\(c\)(当然\(i\)一定要能选到),此时的方案数应该是\(\binom{len+c}{c}\)

可以理解为我们要在\([0,len]\)这个范围内选出\(c\)个数使得非\(0\)数递增,那么我们让前面是\(1\sim len\),然后在最后面放\(c\)\(0\),此时选到\(0\)的位置就表示这个位置是\(0\),否则就按选择的非零数填到后面没有选择的\(0\)的位置。

那么现在就有转移

\[f_{i,j}=\sum_{k=0}^{i-1}\binom{len+c}{c}\sum_{l=0}^{j-1}f_{k,l} \]

后面那个前缀和优化一下时间复杂度就能到\(O(n^3)\)了,写起来细节有一点多。


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=1010,P=1e9+7;
ll n,a[N],b[N],p[N],inv[N],fac[N];
ll ans,f[N][N],g[N][N];
int main()
{
    // printf("%d",sizeof(f)>>20);
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++){
        scanf("%lld%lld",&a[i],&b[i]);
        p[i]=a[i];b[i]++;p[i+n]=b[i];
    }
    inv[1]=1;
    for(ll i=2;i<=n;i++)
        inv[i]=P-(P/i)*inv[P%i]%P;
    inv[0]=1;
    for(ll i=1;i<=n;i++)
        inv[i]=inv[i-1]*inv[i]%P;
    sort(p+1,p+1+2*n);
    ll m=unique(p+1,p+1+2*n)-p-1;
    for(ll i=0;i<m;i++)g[0][i]=1;
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<m;j++){
            if(p[j]>=a[i]&&p[j+1]<=b[i]){
                ll l=p[j+1]-p[j];fac[0]=1;
                for(ll k=1;k<=n;k++)
                    fac[k]=fac[k-1]*(l+k-1)%P;
                for(ll k=i-1,c=1;k>=0;k--){
                    (f[i][j]+=fac[c]*inv[c]%P*g[k][j-1]%P)%=P;
                    if(p[j]>=a[k]&&p[j+1]<=b[k])c++;
                }
            }
            g[i][j]=(g[i][j-1]+f[i][j])%P;
        }
    }
    for(int i=1;i<=n;i++)
        (ans+=g[i][m-1])%=P;
    printf("%lld\n",ans);
    return 0;
}
posted @ 2021-01-14 08:26  QuantAsk  阅读(62)  评论(0编辑  收藏  举报