标签
数位dp + 性质
性质1
对于一个各数位上的数递增的数(如 1479 ),这个数可以表示为 1111+1111+11+11+1... 这样的形式,具体如下 :
设 f(x,j) 表示 x 中有 f(x,j) 个大于等于 j 的数位。
那么 x=∑i=19∑j=1f(x,i)10j−1
不形式化的来说,如 3459 可以表示为一下几个数的和。
111111111111111111111
一个 9 行,对于第 i 行, 其中 1 的个数是原数中大于等于 i 的数位的个数。
思路
那么得到这个性质后有什么用呢?,我们发现对于 1−9 每一个数,在运用这个性质后就是独立的了,所以我们可以对于1−9 每一个数单独做一遍数位 dp , 设 f[i][j][0/1] 表示当前是第 i 位,要 dp 的数出现了 j 次的方案数,0/1 至少用来维护是否压上界.
1−9 每一个数的贡献就是 ∑i=1len(f[len][i][0]+f[len][i][1])∑j=1i10j−1 即方案数乘每种情况的贡献
然后把每个数的贡献加起来就行。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include <iostream> #include <cstdio> #include <cstring> #define ll long long
using namespace std;
const int N = 710; const ll mod = 1e9 + 7; ll f[2 * N][2 * N][2]; int n, len, a[N]; char ch[N]; ll ans;
int add(int a, int b) { return a + b >= mod ? a + b - mod : a + b; }
int main() { scanf("%s", ch + 1); len = strlen(ch + 1); for (int i = 1; i <= len; ++i) { a[i] = ch[i] - '0'; } for (int d = 1; d <= 9; ++d) { memset(f, 0, sizeof(f)); f[0][0][1] = 1; for (int i = 0; i <= len - 1; ++i) { for (int j = 0; j <= i; ++j) { for (int k = 0; k <= 9; ++k) { if (k >= d) f[i + 1][j + 1][0] = add(f[i + 1][j + 1][0], f[i][j][0]); else f[i + 1][j][0] = add(f[i + 1][j][0], f[i][j][0]); } for (int k = 0; k < a[i + 1]; ++k) { if (k >= d) f[i + 1][j + 1][0] = add(f[i + 1][j + 1][0], f[i][j][1]); else f[i + 1][j][0] = add(f[i + 1][j][0], f[i][j][1]); } if (a[i + 1] >= d) f[i + 1][j + 1][1] = add(f[i + 1][j + 1][1], f[i][j][1]); else f[i + 1][j][1] = add(f[i + 1][j][1], f[i][j][1]); } } ll tmp = 1; for (int i = 1; i <= len; ++i) { ans = add(ans, tmp * add(f[len][i][1], f[len][i][0]) % mod), tmp = add(tmp * 10 % mod, 1); } } cout << ans << endl; return 0; }
|