题解 ABC267G【Increasing K Times】/ SS230912B【序列】

problem

SS230912B:形式化地说,给定序列 \(\{a_i\}\),表示在原序列中 \(i\) 出现了 \(a_i\) 次,再给定 \(k\),表示将原序列划分为最少的连续严格递增子段的个数,求可能的原序列个数,对 \(10^9+7\) 取模。\(n,a_i\leq 50,k\leq 2500\)

ABC267G:有多少 \(n\) 阶排列 \(p\) 满足 \(\sum_{i=1}^{n-1}[a_{p_i}<a_{p_{i+1}}]=k\) 等价于有 \(n-k\) 个极长严格递增子段。

solution

插入 DP,设 \(f_{i-1,j}\) 表示已经考虑了 \(<i\) 的数字,当前有 \(j\) 个极长上升连续段,设 \(<i\) 的数字实际上有 \(s\) 个。然后考虑插入 \(i\),情况分为三类:

  • 插在某个连续段后面,不影响段数
  • 插在某个数字前面,不能是连续段开头的前面(除了最开头的前面),一共 \(s-j+1\) 个位置,增加段数
  • 插在第一类的后面使他重复,增加段数

\(c_1,c_2,c_3\) 分别是三类的个数,那么方案数是 \(\binom{j}{c_1}f(c_2,s-j+1)f(c_3,c_1)\),其中 \(f(n,m)=\binom{n+m-1}{m-1}\) 表示 \(n\) 个无标号小球放进 \(m\) 个有标号盒子的方案数。然后 \(\sum_{c_2+c_3=a_i-c_1}f(c_2,s-j+1)f(c_3,c_1)\) 是可以范德蒙德卷积的,证明过程完全一致,可以直接写成 \(f(c_2+c_3=a_i-c_1,s-j+1+c_1)\),然后暴力枚举状态和 \(c_1\) 进行转移即可。\(O(nkV)\)

code

点击查看代码
//O(n^4)
#include <cstdio>
auto __RobinChen__=[](){return freopen("seq.in","r",stdin),freopen("seq.out","w",stdout);}();
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
template<unsigned P> struct modint{
    unsigned v; modint():v(0){}
    template<class T> modint(T x):v((x%int(P)+int(P))%int(P)){}
    modint operator-()const{return modint(P-v);}
    modint inv()const{return qpow(*this,LL(P)-2);}
    modint&operator+=(const modint&rhs){if(v+=rhs.v,v>=P) v-=P; return *this;}
    modint&operator-=(const modint&rhs){return *this+=-rhs;}
    modint&operator*=(const modint&rhs){v=1ull*v*rhs.v%P; return *this;}
    modint&operator/=(const modint&rhs){return *this*=rhs.inv();}
    friend int raw(const modint&self){return self.v;}
    friend modint qpow(modint a,LL b){modint r=1;for(;b;b>>=1,a*=a) if(b&1) r*=a; return r;}
    friend modint operator+(modint lhs,const modint&rhs){return lhs+=rhs;}
    friend modint operator-(modint lhs,const modint&rhs){return lhs-=rhs;}
    friend modint operator*(modint lhs,const modint&rhs){return lhs*=rhs;}
    friend modint operator/(modint lhs,const modint&rhs){return lhs/=rhs;}
    friend bool operator==(const modint&lhs,const modint&rhs){return lhs.v==rhs.v;}
    friend bool operator!=(const modint&lhs,const modint&rhs){return lhs.v!=rhs.v;}
};
typedef modint<int(1e9+7)> mint;
template<int N> struct C_prime{
    mint fac[N+10],ifac[N+10];
    C_prime(){
        fac[0]=1; for(int i=1;i<=N;i++) fac[i]=fac[i-1]*i;
        ifac[N]=1/fac[N]; for(int i=N;i>=1;i--) ifac[i-1]=ifac[i]*i;
    }
    mint operator()(int n,int m){return n>=m?fac[n]*ifac[m]*ifac[n-m]:0;}
};
int n,K,a[110];
mint f[60][2610];
C_prime<1<<17> binom;
mint solve(int n,int m){
    //n 个小球放进 m 个盒子,盒子有标号,球无标号
    return m?binom(n+m-1,m-1):!n;
}
//mint g[2610][60][60];
//void init(){
//    for(int a=1;a<=2560;a++){
//        for(int b=0;b<=50;b++){
//            for(int c=0;c<=50;c++) g[a][b][c]=solve(c,a+b);
//        }
//    }
//}
mint dp(){
    f[1][a[1]]=1;//f[i][j] 表示 a[i] 考虑了之后有多少个上升段
    int s=a[1];
    for(int i=2;i<=n;i++){
        for(int j=0;j<=K&&j<=s;j++){
            if(f[i-1][j]==0) continue;
            //debug("f[%d][%d]=%d\n",i-1,j,raw(f[i-1][j]));
            for(int c1=0;c1<=a[i];c1++){//尾接不增加个数
                int c2=a[i]-c1;
//                for(int c3=0;c3<=c2;c3++){//前插
//                    f[i][j+c2]+=f[i-1][j]*binom(j,c1)*solve(c3,s-j+1)*solve(c2-c3,c1);
//                    //debug("c1=%d,c2=%d,c3=%d,s=%d,f[%d][%d]<=f[%d][%d]*%d*%d*%d\n",c1,c2,c3,s,i,j+c2,i-1,j,binom(j,c1),solve(c3,s-j+1),solve(c2-c3,c1));
//                }
                f[i][j+c2]+=f[i-1][j]*binom(j,c1)*solve(c2,s-j+1+c1);
            }
        }
        s+=a[i];
    }
    return f[n][K];
}
int main(){
//  #ifdef LOCAL
//      freopen("input.in","r",stdin);
//  #endif
    scanf("%d%d",&n,&K);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    printf("%d\n",raw(dp()));
    return 0;
}
posted @ 2023-09-13 18:59  caijianhong  阅读(10)  评论(0编辑  收藏  举报