Fork me on GitHub

「BZOJ1026/SCOI2009」windy数 数位dp

Links there:BZOJ1026-windy数

题意

定义不含前导零且相邻两个数字之差至少为2的正整数被称为windy数.给出$A,B$,求在$A,B$之间windy数的个数.

思路

(别问我为什么这么晚才来补题)

令$f[i][j]$表示当前数字有$i$位,最高位取到$j$以下的满足条件的windy数.

很明显有转移$f[i][j] = \sum f[i-1][k] * (|k - j|>=2)$

预处理出$f[i][j]$后来迭代或者递归计算$x$以内的答案即可 注意细节讨论.

在计算$calc(x)$的时候,先处理$x$的位数和各数位的值.

令位数为$cnt$,当$i<=n-1$的时候直接统计.

$j = 最高位-1$ 独立计算一次 rep(j,1,a[cnt]-1) ret+=f[cnt][j];

对于剩余的位数$ret += f[i][j],cnt-1\geq i \geq 1 ,0 \leq j \leq a[i] - 1$.

然后要保证$|a[i]-a[i+1] |>= 2$,否则直接退出.

差不多就这样了.

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
//my vegetable has exploded. :(
#include<bits/stdc++.h>
#define max(x,y) (x>y?x:y)
#define min(x,y) (x<y?x:y)
#define MM(x,y) memset(x,y,sizeof(x))
#define MCPY(a,b) memcpy(a,b,sizeof(b))
#define pb push_back
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,b,a) for(int i=b;i>=a;i--)
#define fi first
#define se second
using namespace std;
#define int long long

inline int quickpow(int m,int n,int p){int b=1;while(n){if(n&1)b=b*m%p;n=n>>1;m=m*m%p;}return b;}
inline int getinv(int x,int p){return quickpow(x,p-2,p);}
inline int read(void){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){f=ch=='-'?-1:1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x * f;
}
int a[15],cnt=0,f[15][15];
///------------------head------------------

inline void init(void){
rep(i,0,9) f[1][i]=1;
rep(i,2,13)
rep(j,0,9)
rep(k,0,9)
if (abs(k-j)>=2) f[i][j] += f[i-1][k];
}
inline int calc(int x){
cnt=0;
int ret=0;
while(x){a[++cnt]=x%10;x/=10;}
rep(i,1,cnt-1)
rep(j,1,9) ret+=f[i][j];
rep(j,1,a[cnt]-1) ret+=f[cnt][j];
per(i,cnt-1,1){
rep(j,0,a[i]-1) ret+=f[i][j]*(abs(a[i+1]-j)>=2);
if (abs(a[i]-a[i+1])<2) break;
if (i==1) ++ret;
}
return ret;
}

signed main(signed argc, char *argv[]){
init();
int a=read(),b=read();
printf("%lld\n",calc(b)-calc(a-1));
return 0;
}