组合数计数+卡常优化+dp——cf895D

大致思路是数位dp里当前位limit的那一套思想,从前往后递推计算,卡常数比较恶心

/* 
i:1->n
枚举第i位[a,z] 
第i位<s[i]:那么后面就是个全排列,维护一个当前已经用掉的字符桶use,算出贡献直接加到答案里
    算贡献:(n-i)!/(cnta-usea)!...(cntz-usez)!
    如果暴力求复杂度得1e6*26*26
        所以我们预处理好一个tmp=(n-i)!/(cnta-usea)!...(cntz-usez)!
        当第i位选择ch时,将(cnt[ch]-use[ch])!这个底数换成(cnt[ch]-use[ch]-1)!即可 
            
第i为==s[i]:后面的排列个数需要通过i+1位来确定,所以将第i位确定为s[i],更新use即可
 
*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define mod 1000000007
#define N 1000005

char a[N],b[N];
ll n,cnt[N],use[N],F[N],invf[N],inv[N];
inline ll mul(ll a,ll b){
    return a*b%mod;
}

ll solve(char *s){
    ll res=0;
    memset(use,0,sizeof use);
    
    for(int i=1;i<=n;i++){//前i-1位都和s相同,第i位遍历[a,z] 
        int equal=0;
        ll tmp=F[n-i],tmpp;
        for(int k=0;k<26;k++)//先预处理一个值,防止后面累乘 
            if(cnt[k]>use[k])
                tmp=mul(tmp,invf[cnt[k]-use[k]]);
        tmpp=tmp;
        
        for(int j=0;j<26;j++){//第i位放j 
            if(cnt[j]-use[j]==0)continue;//已经用完了
            if(j<s[i]-'a'){
                tmp=mul(tmp,F[cnt[j]-use[j]]);//消去原来j底数的影响 
                tmp=mul(tmp,invf[cnt[j]-use[j]-1]);//增加j-1作为底数 
                res=(res+tmp)%mod;
                tmp=tmpp;//恢复到原来的tmp 
            } 
            else if(j==s[i]-'a'){ 
                use[j]++;
                equal=1;break;
            }
        }
        if(!equal)break;
    }
    
    return res;
}

int main(){
    scanf("%s%s",a+1,b+1);
    //for(int i=1;i<=1000000;i++)a[i]='z',b[i]='z'; 
    n=strlen(a+1);
    /*if(n>=10000){
        cout<<n;return 0;
    }*/
    F[0]=1;invf[0]=1;inv[1]=1;
    for(int i=2;i<=1000000;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    for(int i=1;i<=1000000;i++)F[i]=F[i-1]*i%mod;
    for(int i=1;i<=1000000;i++)invf[i]=invf[i-1]*inv[i]%mod;
    
    for(int i=1;i<=n;i++)cnt[a[i]-'a']++;
        
    cout<<(solve(b)-solve(a)-1+mod)%mod<<'
';
}
原文地址:https://www.cnblogs.com/zsben991126/p/12295394.html