山谷数
题目链接:ybt高效进阶5-3-5
题目大意
问你 1~n 中有多少个数是山谷数。
当一个数字,从高位到低位的数字没有出现先递增再递减的现象,这个数字就是山谷数。
思路
首先吐槽一下:
题目意思一定要看懂,我一开始还以为要整个数字呈现上升然后下降才不是山谷数,搞了半天都是爆蛋。
而且!!!要取模!!!
(我看半天楞是找不到哪里说要取模,然后答案是对
1
e
9
+
7
1e9+7
1e9+7 取模)
接着讲讲做法,很明显的数位 DP。(这里用记忆化搜索)
因为看到递增递减,不难想到设
f
i
,
j
,
k
f_{i,j,k}
fi,j,k 为高位的
i
i
i 位搞定,最后一位选的是
j
j
j,当前状态时
k
k
k 的方案数。
(
k
=
−
1
k=-1
k=−1 表示只有一个有效的数,
k
=
0
k=0
k=0 表示最后部分是递增,
k
=
1
k=1
k=1 表示最后部分是递减的)
然后跑的加记录一下是否还有前导 0 0 0,有就单独处理一下,否则就是看 k k k 的变化,然后注意判断一下不要出现“山峰”。
然后就好啦~
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define mo 1000000007
using namespace std;
struct gj {
int a[101], n;
}a;
int T;
char c;
ll f[101][10][3], ans;
void Clean(gj &x) {
x.n = 0;
memset(x.a, 0, sizeof(x.a));
}
ll work(int now, int pre, int op, int h0, int lim) {
if (!now) return !h0;//记得除去全部都是 0 的情况
if (!lim && f[now][pre][op + 1] != -1) return f[now][pre][op + 1];
//记忆化搜索(能随便填才可以记忆化)
int maxn = lim ? a.a[now] : 9;
ll re = 0;
for (int i = 0; i <= maxn; i++) {
if (op == 0 && i < pre) continue;
//原本前面都是 0,这个可能是第一个数
if (h0) re = (re + work(now - 1, i, op, (i == 0), lim && (i == a.a[now]))) % mo;
//两个数相等,不会改变单调性
else if (i == pre) re = (re + work(now - 1, i, op, 0, lim && (i == a.a[now]))) % mo;
//看单调性是否改变
else re = (re + work(now - 1, i, (i < pre ? 1 : 0), 0, lim && (i == a.a[now]))) % mo;
//lim && (i == a.a[now]) 就是判断是否压满
}
if (!lim) f[now][pre][op + 1] = re;
return re;
}
int main() {
scanf("%d", &T);
while (T--) {
c = getchar();
while (c < '0' || c > '9') c = getchar();
Clean(a);
while (c >= '0' && c <= '9') {
a.a[++a.n] = c - '0';
c = getchar();
}
for (int i = 1; i <= a.n / 2; i++)
swap(a.a[i], a.a[a.n - i + 1]);
ans = 0;
memset(f, -1, sizeof(f));
for (int i = 0; i <= a.a[a.n]; i++)//先搞最高位
ans = (ans + work(a.n - 1, i, -1, (i == 0), (i == a.a[a.n]))) % mo;
printf("%lld\n", ans);
}
return 0;
}