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:將每個數選擇的個數的狀態壓縮成一個數來維護。
於是,我們設 表示第 位,選數的狀態爲 ,對於m取餘的結果爲 的方案數。
狀態轉移方程如下:
其中, 爲每次枚舉當前加入的數字, 爲在 的基礎上添加 壓縮後在狀態,注意時刻要保證當前 的個數不能大於 中 的個數。
最後的答案則爲 ,其中 爲 的數字位數, 爲 中數字出現的狀態。
因爲時間過大,維護 表示 中是否至少有一位有值,因爲我們記錄的是方案數,所以如果都是 時就可以跳過了。每次更新 時把 即可。
同時,由於空間過大,要使用滾動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;
}