• [bzoj4361] isn [树状数组+dp+容斥原理]


    题面

    传送门

    思路

    首先,本题目的核心元素是非降子序列,而显然这个题目中的子序列只和序列的长度、位置,以及互相之间的包含关系,这些东西相关

    所以我们可以依据这些先“猜”(实际上是估测一个类似的)$dp$方程:

    设$dp[i][j]$表示以$i$个位置结尾的,长度为$j$的非降子序列个数

    转移:$dp[i][j]=sum_{k=1}^{i-1}[a[k]<a[i]]dp[k][j-1]$

    这个东西显然可以用树状数组求逆序对的套路,在$O(n^2logn)$的时间内求出来

    然后,我们定义$g[i]$表示剩下掉$i$个数,得到一个非降子序列的方法数,方程显然:

    $g[i]=sum_{j=1}^{n}dp[j][i]*(i!)$

    注意不要漏掉了阶乘,这个是表示组合方法的

    但是这个东西是有问题的:在去掉$i$个数的过程中,有可能在去掉第$j(j<i)$个数的时候就已经达成非降子序列、不能继续操作了

    那么我们就需要容斥一下

    我们令$ans[i]$表示减去了这些不合法方案以后的$g[i]$

    考虑一个$j>i$的数对$(j,i)$,如果在$j$处就已经达成的话,方案数等于$ans[j]$,然后我们要从$i$个里面选出来$j-i$个作为在完成以后还删掉了的

    那么显然重复的方案数就是$ans[j] ast C_{j}^{j-i}ast(j-i)!$

    对于所有的$j>i$,把$g[i]$减掉上面那个东西得到$ans[i]$,然后所有$ans[i]$的和就是答案了

    Code

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<cstdio>
    #include<cmath>
    #include<cassert>
    #define ll long long
    #define MOD 1000000007
    #define rank DEEP_DARK_FANTASY
    using namespace std;
    inline int read(){
        int re=0,flag=1;char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-') flag=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
        return re*flag;
    }
    ll qpow(ll a,ll b){
        ll re=1;
        while(b){
            if(b&1) re=re*a%MOD;
            a=a*a%MOD;b>>=1;
        }
        return re;
    }
    void add(ll &x,ll y){
        x+=y;
        if(x>=MOD)x-=MOD;
    }
    void dec(ll &x,ll y){
        x-=y;
        if(x<0) x+=MOD;
    }
    struct BIT{
        ll a[2010];
        BIT(){memset(a,0,sizeof(a));}
        int lowbit(int x){
            return x&(-x);
        }
        void update(int x,ll val){
            for(;x<=2000;x+=lowbit(x)) add(a[x],val);
        }
        ll sum(int x){
            ll re=0;
            for(;x>0;x-=lowbit(x)) add(re,a[x]);
            return re;
        }
    }T[2010];
    int n,a[2010],rank[2010];ll dp[2010][2010],ans[2010],tmp[2010];
    ll f[2010],finv[2010];
    inline bool cmp(int l,int r){
        return a[l]<a[r];
    }
    void init(){
        int i,len=2000;f[0]=f[1]=finv[0]=finv[1]=1;
        for(i=2;i<=len;i++) f[i]=f[i-1]*i%MOD;
        finv[len]=qpow(f[len],MOD-2);
        for(i=len;i>2;i--) finv[i-1]=finv[i]*i%MOD;
    }
    ll C(ll x,ll y){
        return f[x]*finv[y]%MOD*finv[x-y]%MOD;
    }
    int main(){
        n=read();int i,j,cnt=0;ll re=0;
        init();
        for(i=1;i<=n;i++) a[i]=read(),rank[i]=i;
        sort(rank+1,rank+n+1,cmp);
        for(i=1;i<=n;i++){
            cnt++;
            while(a[rank[i]]==a[rank[i+1]]) a[rank[i]]=cnt,i++;
            a[rank[i]]=cnt;
        }
        for(i=1;i<=n;i++){
            for(j=i;j>=2;j--){
                dp[i][j]=T[j-1].sum(a[i]);
                T[j].update(a[i],dp[i][j]);
            }
            dp[i][1]=1ll;
            T[1].update(a[i],1ll);
        }
        for(i=1;i<=n;i++)
            for(j=i;j<=n;j++)
                add(tmp[i],dp[j][i]*f[n-i]%MOD);
        for(i=n;i>=1;i--){
            ans[i]=tmp[i];
            for(j=i+1;j<=n;j++)
                dec(ans[i],ans[j]*C(j,j-i)%MOD*f[j-i]%MOD);
            add(re,ans[i]);
        }
        printf("%lld
    ",re);
    }
    
  • 相关阅读:
    解决CentOS6.5虚拟机克隆后无法上网(网卡信息不一致)的问题
    Linux密码保护
    破解Linux系统开机密码
    Linux常用命令
    人教版中小学教材电子版下载
    作业一
    实验四
    实验一
    实验三
    实验二
  • 原文地址:https://www.cnblogs.com/dedicatus545/p/9604592.html
Copyright © 2020-2023  润新知