【数位DP】JZOJ 5831. 【NOIP提高A组模拟2018.8.18】 number

JZOJ 5831. 【NOIP提高A组模拟2018.8.18】 number

题目

Description

给定正整数 n,m,问有多少个正整数满足:
1、不含前导 0;
2、是 m 的倍数;
3、可以通过重排列各个数位得到 n。

Input

一行两个整数 n,m。

Sample Input

1 1

Output

一行一个整数表示答案对 998244353 取模的结果。

Sample Output

1

Data Constraint

对于 20%的数据,n<10^10。
对于 50%的数据,n<10^16,m<=20。
对于 100%的数据,n<10^20,m<=100。

题解

看看题目,第3个条件很好地说明了这道题目的算法—数位DP
既然如此,我们想想如何设做,状态怎么设,需要维护哪些条件。不妨对每个条件来做出一定处理。
条件1:DP第1位时从1至9,而其他的是从0至9即可;
条件2:设一维状态表示对于m取余的结果;
条件3:将每个数选择的个数的状态压缩成一个数来维护。
于是,我们设 f [ i ] [ s ] [ j ] 表示第 i 位,选数的状态为 s ,对于m取余的结果为 j 的方案数。
状态转移方程如下:
f [ i + 1 ] [ s ] [ [ ( j 10 + k ) mod m ] = ( f [ i + 1 ] [ s ] [ ( j 10 + k ) mod m ] + f [ i ] [ s ] [ j ] )
其中, k 为每次枚举当前加入的数字, s 为在 s 的基础上添加 k 压缩后在状态,注意时刻要保证当前 k 的个数不能大于 n k 的个数。
最后的答案则为 f [ l e n ] [ S ] [ 0 ] ,其中 l e n n 的数字位数, S n 中数字出现的状态。
因为时间过大,维护 q [ i ] [ s ] = 0 / 1 表示 f [ i ] [ s ] [ 0   m 1 ] 中是否至少有一位有值,因为我们记录的是方案数,所以如果都是 0 时就可以跳过了。每次更新 f [ i + 1 ] [ s ] [ [ ( j 10 + k ) mod m ] 时把 q [ i + 1 ] [ s ] = 1 即可。
同时,由于空间过大,要使用滚动DP。

代码

#include<cstdio> 
#include<cstring>
using namespace std;
int a[15],b[15],g[15],p[60010][110],q[21][60010];
long long f[2][60010][110];
char z[21];
int main()
{
    int m,i,j,k,l,s;
    scanf("%s",z+1);
    scanf("%d",&m);
    int n=strlen(z+1);
    for(i=1;i<=n;i++) a[z[i]-'0']++;
    g[10]=1;
    for(i=9;i>=0;i--) g[i]=g[i+1]*(a[i]+1);
    memset(f,0,sizeof(f));
    for(i=1;i<=9;i++) if(a[i]) f[1][g[i+1]][i%m]=1,q[1][g[i+1]]=1;
    for(i=1;i<n;i++)
    {
        for(j=0;j<g[0];j++) if(q[i][j])
        {
            int t=j;
            for(k=0;k<=9;k++)
            {
                b[k]=t/g[k+1];
                t%=g[k+1];
            }
            for(k=0;k<=9;k++)
            {
                if(b[k]==a[k]) continue;
                b[k]++;s=0;
                for(l=0;l<=9;l++) s+=b[l]*g[l+1];
                for(l=0;l<m;l++) if(f[i%2][j][l])
                {
                    if(p[s][(l*10+k)%m]!=i+1) 
                    {
                        p[s][(l*10+k)%m]=i+1;
                        f[1-i%2][s][(l*10+k)%m]=f[i%2][j][l];
                        q[i+1][s]=1;
                    }
                    else f[1-i%2][s][(l*10+k)%m]+=f[i%2][j][l];
                    f[1-i%2][s][(l*10+k)%m]%=998244353;
                }
                b[k]--;
            }
        }
    }
    printf("%lld",f[n%2][g[0]-1][0]);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39565901/article/details/81812400