bzoj4361: isn【树状数组优化dp+容斥】

本文探讨了一道经典的算法题目,即如何计算将一个序列变为非降序列的不同操作方案的数量。通过动态规划与树状数组相结合的方法,文章提供了一个高效的解决方案,并给出了完整的代码实现。
Description

给出一个长度为n的序列A(A1,A2…AN)。如果序列A不是非降的,你必须从中删去一个数,
这一操作,直到A非降为止。求有多少种不同的操作方案,答案模10^9+7。

Input

第一行一个整数n。
接下来一行n个整数,描述A。

Output

一行一个整数,描述答案。

Sample Input

4

1 7 5 3

Sample Output

18

HINT

1<=N<=2000

解题思路:

先dp出 f[i]f[i] 为长度为i的不下降序列数,可以用树状数组优化到O(n2logn)O(n2logn)

有个直观的想法就是,枚举最终序列的长度k,设长度为k的不下降子序列个数为 f[k]f[k] ,那么有 (nk)!f[k](n−k)!∗f[k]种方案。

但是这样可能会有一些不合法的方案。

注意到合法方案在最后一步删之前不是不降序列,所以可以减去在最后一步删之前是一个长度为k+1k+1的不下降子序列转移来的方案数,即(nk1)!f[k+1](k+1)(n−k−1)!∗f[k+1]∗(k+1)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll getint()
{
    ll i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=2005,mod=1e9+7;
inline void add(int &x,int y){x=x+y>=mod?x+y-mod:x+y;}
int n,m,ans,a[N],b[N],f[N],fac[N];
struct BIT
{
    int a[N];
    inline void insert(int i,int v){for(;i<=n;i+=i&-i)add(a[i],v);}
    inline int query(int i){int res=0;for(;i;i-=i&-i)add(res,a[i]);return res;}
}bit[N];
int main()
{
    //freopen("lx.in","r",stdin);
    n=getint();for(int i=1;i<=n;i++)a[i]=b[i]=getint();
    sort(b+1,b+n+1),m=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+m+1,a[i])-b+1;
    fac[0]=1;for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
    bit[0].insert(1,1);
    for(int i=1;i<=n;i++)
        for(int j=n;j;j--)
        {
            int tmp=bit[j-1].query(a[i]);
            add(f[j],tmp),bit[j].insert(a[i],tmp);
        }
    for(int i=1;i<=n;i++)add(ans,(1ll*f[i]*fac[n-i]%mod-1ll*(i+1)*f[i+1]%mod*fac[n-i-1]%mod+mod)%mod);
    cout<<ans<<'\n';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值