BZOJ4584: [Apio2016]赛艇 DP

题意:长度为N的序列,每个元素可以在[ai,bi]间取值或者不取值,求有多少种不同的上升序列(不能都不取值)
N<=500,元素范围1e9
容易发现取值只会形成2*n个区间,每个元素可以在某几段区间内任意取值
记F[i][j][k]表示前i个元素,最后一段落在j这个区间,这个区间一共落了k个元素的方案数
考虑转移,如果一个区间之前已经有k-1个,现在要加一个,由于取值是任意的,所以相当于把(x个中任取k-1个)变成了(x个中任取k个),求一下组合数的变化量可知相当于f[i-1][j][k-1]*(size-k+1)/k,size是区间长度。如果这个区间之前没有,那么上一个在这个区间之前的任意一个区间都可以,求一下前缀和即可转移。此外可以不选,所以要加上上一次的。
但是OJ评测机比本地慢两倍左右,交上去会被卡常。
考虑优化:倒着枚举j和k,这样j-1和k-1仍然是上一次的结果,j之后的都是这一次的结果,就不用滚动数组了。
代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define gm 501
using namespace std;
typedef long long ll;
const int mob=1000000007;
int n,tot;
int a[gm],b[gm],c[gm<<1];
int f[gm<<1][gm],sum[gm<<1];
int inv[gm];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d",a+i,b+i);
        c[i]=a[i],c[n+i]=b[i]+1;
        if(i==1) inv[i]=1;
        else inv[i]=ll(mob-mob/i)*inv[mob%i]%mob;
    }
    sort(c+1,c+(n<<1)+1),tot=unique(c+1,c+(n<<1)+1)-c-1;
    for(int i=1;i<=n;++i)
    {
        a[i]=lower_bound(c+1,c+tot+1,a[i])-c;
        b[i]=lower_bound(c+1,c+tot+1,b[i]+1)-c;
    }
    f[0][0]=1;
    for(int i=0;i<tot;++i)
    sum[i]=1;
    for(int i=1;i<=n;++i)
    {
        for(int j=tot-1;j;--j)
        {
            int *dp=f[j];
            int size=c[j+1]-c[j];
            if(a[i]<=j&&j<b[i])
            {
                for(int k=size<i?size:i;k>1;--k)
                {
                    dp[k]=(dp[k]+(ll)(dp[k-1])*(size-k+1)%mob*(inv[k]))%mob;
                }
                dp[1]=(dp[1]+(ll)(sum[j-1])*size)%mob;
            }
        }
        for(int j=1;j<tot;++j)
        {
            int *dp=f[j];
            int &nsum=sum[j]=sum[j-1];
            int size=c[j+1]-c[j];
            for(int k=1;k<=size&&k<=i;++k)
            {
                int temp=nsum+dp[k];
                nsum=(temp>=mob)?(temp-mob):(temp);
            }
        }
    }
    printf("%d\n",(sum[tot-1]+mob-1)%mob);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值