BZOJ1026:[SCOI2009]windy数——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=1026
Description
windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,
在A和B之间,包括A和B,总共有多少个windy数?
Input
包含两个整数,A B。
Output
一个整数
Sample Input
【输入样例一】
1 10
【输入样例二】
25 50
1 10
【输入样例二】
25 50
Sample Output
【输出样例一】
9
【输出样例二】
20
9
【输出样例二】
20
————————————————————————————————————
今天开始学数位dp了!
所以趁着这道题还简单赶紧记录下来。
设dp(n)表示0~n的windy数个数。
设f[i][j][0/1]表示当前处理到第i位数为j,此时前i位数比n的前i位数小于等于(为0)/大于(为1)的数的个数。
显然我们求a~b的个数可以利用前缀和,只需要求dp(b)再减去dp(a-1)即可。
那么对于函数dp(n),其主要流程:
1.将n拆成十进制数,存在数组中(这里数组为a,长度为len)
2.特殊处理第一层:
for(int i=0;i<=9;i++){ if(i<=a[1])f[1][i][0]=1; else f[1][i][1]=1; }
3.枚举i=2~len层并处理之,枚举当前层填充的数字j和上一层填充数字k。当符合windy数的条件时开始更新,更新方程较显然就不多说了:
if(j<a[i]) f[i][j][0]+=f[i-1][k][0]+f[i-1][k][1]; else if(j==a[i]) f[i][j][0]+=f[i-1][k][0],f[i][j][1]+=f[i-1][k][1]; else f[i][j][1]+=f[i-1][k][0]+f[i-1][k][1];
4.得出答案。这里需要注意对于最后一层需要特殊处理防止越出0~n的范围。也很显然。
整套流程至此完毕,复杂度显然为log级别。
PS:判断是否为windy数很简单,这里用了一个辅助数组c[i][j]表示abs(i-j),借此判断能够美化代码(滑稽)
#include<cstdio> #include<cstring> using namespace std; const int N=11; int a[N],f[N][N][2],c[N][N]; int dp(int x){ int len=0; while(x)a[++len]=x%10,x/=10; if(len==0)a[++len]=0; memset(f,0,sizeof(f)); for(int i=0;i<=9;i++){ if(i<=a[1])f[1][i][0]=1; else f[1][i][1]=1; } for(int i=2;i<=len;i++){ for(int j=0;j<=9;j++){ for(int k=0;k<=9;k++){ if(c[j][k]>=2){ if(j<a[i]) f[i][j][0]+=f[i-1][k][0]+f[i-1][k][1]; else if(j==a[i]) f[i][j][0]+=f[i-1][k][0],f[i][j][1]+=f[i-1][k][1]; else f[i][j][1]+=f[i-1][k][0]+f[i-1][k][1]; } } } } int ans=0; for(int i=1;i<=a[len];i++)ans+=f[len][i][0]; for(int i=len-1;i;i--){ for(int j=1;j<=9;j++){ ans+=f[i][j][0]+f[i][j][1]; } } return ans; } int main(){ for(int i=0;i<=9;i++){ for(int j=i;j<=9;j++){ c[i][j]=c[j][i]=j-i; } } int a,b; scanf("%d%d",&a,&b); printf("%d\n",dp(b)-dp(a-1)); return 0; }