题目描述
给 n < = 1 0 700 n<=10^{700} n<=10700,问1到n中每个数在各数位排序后得到的数的和。答案膜1e9+7。
Sol
神仙套路:
假设
X
=
a
n
a
n
−
1
a
n
−
2
…
a
1
‾
X=\overline{a_na_{n-1}a_{n-2}\dots a_1}
X=anan−1an−2…a1是一个每个位置上的数都严格上升
那么可以把
X
X
X表示为
∑
d
[
i
]
∗
1111111
…
11
‾
共
i
个
1
\sum d[i]*\underline {1111111\dots11}_{共i个1}
∑d[i]∗1111111…11共i个1其中
d
[
i
]
d[i]
d[i]表示第
i
i
i 为和第
i
+
1
i+1
i+1 的差值(其实任何一个数都能这样表示)
然后我们可以把数 X X X理解为一个阶梯状的东西,强行把差值换一种解释:
d [ i ] = d[i]= d[i]= 数 k ∈ [ 0 , 9 ] k\in[0,9] k∈[0,9] 的个数 , 满足在所有的数位中 , 大于数 k k k 的数的个数恰好有 i i i 个
然后就直接暴力设状态 d p [ 0 / 1 ] [ 0 … 9 ] [ i ] [ j ] dp[0/1][0\dots9][i][j] dp[0/1][0…9][i][j] 表示到了第 i i i 位 , 安全态还是危险态下 , 大于数 k k k 的位数有 j j j 个的方案数,最后算贡献
为什么要记录方案数? 因为我们已经知道每一个 d [ i ] d[i] d[i] 要乘上的系数了,只需要求出每一种情况下的 d [ i ] d[i] d[i] , 而每一种情况下的贡献是独立的,不会重复 , 因此只要算出方案数就行了
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<algorithm>
#include<set>
using namespace std;
const int N=800;
const int mod=1e9+7;
char s[N];
int a[N],len,ans=0;
int dp[2][10][N][N];// 是否安全态下 到了第 i 位 大于 数字 k 的有 j 个的方案数
int fac[N];
typedef long long ll;
inline void upd(int &x,int y){x+=y;if(x>=mod) x-=mod;}
int main()
{
scanf("%s",s+1);
len=strlen(s+1);
for(register int i=1;i<=len;++i) a[i]=s[i]-'0';
fac[1]=1;
for(register int i=2;i<=len;++i) fac[i]=(1ll*fac[i-1]*10+1)%mod;
for(register int i=0;i<=9;++i) dp[1][i][0][0]=1;
for(register int i=1;i<=len;++i) {
for(register int p=0;p<=9;++p){
for(register int j=0;j<=i;++j){
for(register int k=0;k<=9;++k){
if(p>k) {
if(j==0) continue;
upd(dp[0][k][i][j],dp[0][k][i-1][j-1]);
if(p<a[i]) upd(dp[0][k][i][j],dp[1][k][i-1][j-1]);
if(p==a[i]) upd(dp[1][k][i][j],dp[1][k][i-1][j-1]);
}
else{
upd(dp[0][k][i][j],dp[0][k][i-1][j]);
if(p<a[i]) upd(dp[0][k][i][j],dp[1][k][i-1][j]);
if(p==a[i]) upd(dp[1][k][i][j],dp[1][k][i-1][j]);
}
}
}
}
}
register int ans=0;
for(register int i=1;i<=len;++i){
for(register int j=0;j<=9;++j){
upd(ans,1ll*fac[i]*(dp[0][j][len][i]+dp[1][j][len][i])%mod);
}
}
printf("%d\n",ans);
}