[航海协会]序列计数问题

序列计数问题

题目概述

在这里插入图片描述
在这里插入图片描述

题解

首先看到这题目,应该比较容易想到生成函数。
i i i个数的生成函数显然是 F i = ∑ j = 0 b i − c x j = 1 − x b i − c + 1 1 − x F_i=\sum_{j=0}^{b^i-c}x^j=\frac{1-x^{b^i-c+1}}{1-x} Fi=j=0bicxj=1x1xbic+1
答案要求 ∑ x i < n \sum x_i<n xi<n,相当于就是全部乘起来,做个前缀和取第 n − 1 n-1 n1项,
其中,我们定义 f ( S ) = ∑ x ∈ S b x f(S)=\sum_{x\in S}b^x f(S)=xSbx表示
A n s = [ x n − 1 ] ∏ i = 1 m 1 − x b i − c + 1 ( 1 − x ) m + 1 = [ x n − 1 ] ∑ S ⊂ U ( − 1 ) ∣ S ∣ x f ( S ) − ( c − 1 ) ∣ S ∣ ( 1 − x ) m + 1 A n s = ∑ S ⊂ U ( − 1 ) ∣ S ∣ ( n + m − 1 + ( c − 1 ) ∣ S ∣ − f ( S ) m ) Ans=[x^n-1]\frac{\prod_{i=1}^m 1-x^{b^i-c+1}}{(1-x)^{m+1}}=[x^n-1]\frac{\sum_{S\subset U}(-1)^{|S|}x^{f(S)-(c-1)|S|}}{(1-x)^{m+1}}\\ Ans=\sum_{S\subset U}(-1)^{|S|}\binom{n+m-1+(c-1)|S|-f(S)}{m} Ans=[xn1](1x)m+1i=1m1xbic+1=[xn1](1x)m+1SU(1)Sxf(S)(c1)SAns=SU(1)S(mn+m1+(c1)Sf(S))直接枚举集合 S S S可以做到 O ( 2 m m ) O\left(2^mm\right) O(2mm),但都做到这里了我们何必暴力枚举集合呢?
考虑再给它化一下,
A n s = 1 m ! ∑ S ⊂ U ( n + m − 1 + ( c − 1 ) ∣ S ∣ − f ( S ) ) m ‾ Ans=\frac{1}{m!}\sum_{S\subset U}(n+m-1+(c-1)|S|-f(S))^{\underline m} Ans=m!1SU(n+m1+(c1)Sf(S))m显然可以数位 d p dp dp,我们可以将后面的下降幂看成一个多项式,然后 d p dp dp维护。
为了同时保证后面下降幂那东西不小于 m m m,我们枚举的 f ( S ) f(S) f(S)显然得有一个上限,不能超过 n + m − 1 + ( c − 1 ) ∣ S ∣ n+m-1+(c-1)|S| n+m1+(c1)S,这个上限与 ∣ S ∣ |S| S有关,所以我们可以考虑先枚举 ∣ S ∣ |S| S,再根据当前的上限数位 d p dp dp
定义 d p i , j , k , 0 / 1 dp_{i,j,k,0/1} dpi,j,k,0/1表示在前 i i i位中,选择了 j j j位,并且是否达到上限的集合 S S S f ( S ) k f(S)^k f(S)k次方之和。
转移显然就是每次枚举是否加入当前的 2 i 2^i 2i嘛,但由于需要转移每个次方的值,我们的复杂度会达到惊人的 O ( m 5 ) O\left(m^5\right) O(m5),考虑优化。

注意到 2 − b ⩽ c ⩽ b − 1 2-b\leqslant c\leqslant b-1 2bcb1,显然,当 b > 2 b>2 b>2时,有 ( b n − c + 1 ) ⩾ ∑ i = 1 n − 1 ( b i − c + 1 ) (b^n-c+1)\geqslant \sum_{i=1}^{n-1}(b^i-c+1) (bnc+1)i=1n1(bic+1)
也就是说,我们可以尝试求出当前能够选择的字典序最大 S S S,显然,比这字典序小的 S S S都是可以被选择的。
这样我们就可以只对一个 n n n进行数位 d p dp dp了,优化到 O ( m 4 ) O\left(m^4\right) O(m4)
对于 b = 2 , c = 1 b=2,c=1 b=2,c=1时,上面的结论依旧成立。
对于 b = 2 , c = 0 b=2,c=0 b=2,c=0时,一种简单的方法就是先将所有小于 n n n f ( S ) f(S) f(S)全部计算,再减掉 f ( S ) + ∣ S ∣ ⩾ n f(S)+|S|\geqslant n f(S)+Sn的部分的贡献。
但我们不妨继续思考一下我们上面的方法是否能够得到正确的答案,毕竟笔者懒得分类讨论。
我们定义 A > B A>B A>B为集合 A A A的字典序比集合 B B B的字典序大。
如果 S ′ ⊂ S S'\subset S SS,由于 ∀ i ∈ [ 1 , ∞ ) , b i − c + 1 > 0 \forall i\in[1,\infty),b^i-c+1>0 i[1,),bic+1>0,所以 f ( S ) > f ( S ′ ) ∧ S > S ′ f(S)>f(S')\wedge S>S' f(S)>f(S)S>S
所以如果我们直接枚举每个点加入是否符合 < n <n <n的话一定可以找到满足 f ( S ) < n f(S)<n f(S)<n的字典序最大集合 S S S
显然,对于 S ′ < S , f ( S ′ ) < f ( S ) S'<S,f(S')<f(S) S<S,f(S)<f(S),由于 ∣ S ′ ∣ − ∣ S ∣ ⩽ m |S'|-|S|\leqslant m SSm,所以 f ( S ′ ) + ∣ S ′ ∣ − f ( S ) − ∣ S ∣ ⩽ m f(S')+|S'|-f(S)-|S|\leqslant m f(S)+Sf(S)Sm
这也就是说上面的 n + m − 1 + ( c − 1 ) ∣ S ∣ − f ( S ) ⩾ 0 n+m-1+(c-1)|S|-f(S)\geqslant 0 n+m1+(c1)Sf(S)0
如果 ( c − 1 ) ∣ S ∣ − f ( S ) ⩾ n (c-1)|S|-f(S)\geqslant n (c1)Sf(S)n,也不过是一个在 [ 0 , m ) [0,m) [0,m)之间数的 m m m次下降幂罢了,它肯定经过 0 0 0,贡献也自然是 0 0 0,没啥用。
所以采用我们上面的方法是一定可以得到正确答案的。

时间复杂度 O ( m 3 ) O\left(m^3\right) O(m3)

源码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
typedef unsigned int uint;
#define MAXN 505
#define MAXM (1<<15)+5
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
const int lim=1000000;
const LL INF=0x3f3f3f3f3f3f3f3f;
const int mo=1e9+7;
template<typename _T>
void read(_T &x){
   _T f=1;x=0;char s=getchar();
   while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
   while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
   x*=f;
}
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int N,m,B,c,len,a[MAXN],b[MAXN*4],lena,totb,g[55];
int pwb[55],ff[55],ans,dp[55][55][2],C[55][55],f[55];
char str[MAXN];bool mark[55];
void work(){
    for(int i=1;i<=totb;i++){
        if(b[i]<0)b[i+1]--,b[i]+=B;
        if(b[i]>=B)b[i+1]++,b[i]-=B;
    }
    while(b[totb+1])totb++;
    while(totb&&!b[totb])totb--;
}
signed main(){
    //freopen("seqcnt.in","r",stdin);
    //freopen("seqcnt.out","w",stdout);
    scanf("%s",str+1);read(m);read(B);read(c);
    len=(int)strlen(str+1);pwb[0]=1;
    for(int i=0;i<=m;i++){
        C[i][0]=C[i][i]=1;
        for(int j=1;j<i;j++)
            C[i][j]=add(C[i-1][j-1],C[i-1][j],mo);
    }
    ff[1]=1;for(int i=2;i<=m;i++)ff[i]=1ll*(mo-mo/i)*ff[mo%i]%mo;
    for(int i=1;i<=m;i++)pwb[i]=1ll*B*pwb[i-1]%mo;
    for(int i=1;i<=len;i++)N=(10ll*N+str[i]-'0')%mo;
    for(int i=len,now=1,j=1;i>0;i--){
        a[j]+=now*(str[i]-'0'),now*=10;
        lena=j;if(now==lim)now=1,j++;
    }
    while(lena){
        int now=0;
        for(int i=lena;i>0;i--){
            LL tp=1ll*lim*now+a[i];
            now=tp%B;a[i]=tp/B;
        }
        b[++totb]=now;
        while(lena&&!a[lena])lena--;
    }
    b[1]--;work();Add(N,m,mo);
    b[1]+=(c-1);work();
    if(totb>m+1)mark[m+1]=1;
    else for(int i=m;i>0;i--){
        //for(int j=1;j<=totb;j++)printf("%d ",b[j]);puts("");
        bool flag=0;for(int j=i+1;j<=totb;j++)if(b[j])flag|=1;
        if(!flag)continue;b[i+1]--;mark[i]=1,b[1]+=(c-1),work();
        if(!totb||b[totb]<0)break;
    }
    dp[m][0][!mark[m+1]]=1;
    for(int i=m;i>0;i--){
        int tp=(1ll*pwb[i]+mo-c+1)%mo;g[0]=1;
        for(int j=1;j<=m;j++)g[j]=1ll*tp*g[j-1]%mo;
        for(int j=0;j<=m;j++)for(int k=0;j+k<=m;k++)
            Add(dp[i-1][j+k][0],mo-1ll*dp[i][j][0]*g[k]%mo*C[j+k][k]%mo,mo);
        for(int j=0;j<=m;j++)Add(dp[i-1][j][0],dp[i][j][0],mo);
        if(mark[i])for(int j=0;j<=m;j++)for(int k=0;j+k<=m;k++)
            Add(dp[i-1][j+k][1],mo-1ll*dp[i][j][1]*g[k]%mo*C[j+k][k]%mo,mo);
        for(int j=0;j<=m;j++)Add(dp[i-1][j][!mark[i]],dp[i][j][1],mo),g[j]=0;
    }
    g[0]=1;
    for(int i=1;i<=m;i++)for(int j=m;j>0;j--)
        Add(g[j],1ll*add(N,mo-i,mo)*g[j-1]%mo,mo);
    for(int i=0;i<=m;i++){
        Add(f[i],dp[0][i][0],mo),
        Add(f[i],dp[0][i][1],mo);
        if(i&1)f[i]=mo-f[i];
    }
    for(int i=0;i<=m;i++)Add(ans,1ll*g[i]*f[m-i]%mo,mo);
    for(int i=1;i<=m;i++)ans=1ll*ff[i]*ans%mo;
    printf("%d\n",ans);
    return 0;
}

谢谢!!!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值