P2657 【[SCOI2009]windy数】 数位DP板子题
转自 https://www.luogu.com.cn/blog/ljc20020730/solution-p2657
一起学下数位dp
- 设f(x)表示x前面的数 即 t∈[1,x) 中windy数的个数
那么显然如果要求 [l,r]中windy数的个数就是:
- F(l,r) = f(r+1)-f(l)
数位dp开始,预处理位数为i最高位为j的windy数个数 f[i][j]
转移:
- f[i][j]=f[i-1][k] | 其中k是非负整数k∈[0,9]且|k-j|>=2
初始值:
- f[1][i]=1 | 其中i为非负整数 i∈[0,9]
求f(x)怎么求呢?
为了方便处理先对数x进行按10进制位拆分到a[]数组
- 显然位数比x要小的数字都是合法的都在[1,x)区间内,直接统计就行
- 位数和x一样最高位的数字比x小的数字都是合法的都在[1,x)区间内直接统计就行
- 位数和x一样,最高位又和x一样我们从左到右扫一遍x各个位子上的数字大小然后枚举合法的该位子上的数[0,9]判断是否合法就行。
ll f[15][15]; int a[15]; ll fun(ll x) { int len=0; while (x>0ll) a[++len]=x%10,x/=10; ll sum=0ll; for (int i=1;i<=len-1;i++) for (int j=1;j<=9;j++) sum+=f[i][j]; for (int i=1;i<a[len];i++) sum+=f[len][i];
//这最后一个循环 其实没包括X这个数本身啊 算的是1~x-1的 所以下面调用的时候是m+1
for (int i=len-1;i>=1;i--) { for (int j=0;j<=a[i]-1;j++) if (abs(j-a[i+1])>=2) sum+=f[i][j]; if (abs(a[i+1]-a[i])<2) break; //这一位不行后面的位子一定不行 } return sum; } int main() { ll n,m; scanf("%lld %lld",&n,&m); if (n>m) swap(n,m); for (int i=0;i<=9;i++) f[1][i]=1ll; for (int i=2;i<=10;i++) for (int j=0;j<=9;j++) for (int k=0;k<=9;k++) if (abs(k-j)>=2) f[i][j]+=f[i-1][k]; printf("%lld\n",fun(m+1)-fun(n)); return 0; }