题解 P2657 【[SCOI2009]windy数】

首先我们先看题目

windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。

1-观察题目,判定类型

从题目中可以画出关键词:不含前导零而且是相邻两个数字,观察数据范围: 100%的数据,满足 1 <= A <= B <= 2000000000 。我们就可以明显看出这一题是一道数位dp的模版。

2-推出状态转移方程

因为题目给的条件很清晰,我们可以得到:我们应该设一个二维的数组,其中一维用来表示第i位,另一维则表示该位上的数字是j(0<=j<=9)(我们在动态规划时的转移数组必须跟答案有密切的关系,而且可以巧妙地利用题目上的条件,在进行每一步转移时一定是具有局部性的。)

树形dp的特点:具有按位处理的特点,处理的问题一般都是数据较大的数字问题,并且每一位与其相邻位之间有联系。并且树形dp一般不能在dp部分完成后直接出答案,转移一般比较简单,但是答案的计算很复杂。

3-进行答案的计算(假设预处理dp过程结束)

这类题一般问一个区间内的某种数的个数,因为计算区间很麻烦所以我们用区间上界减去下界的方法进行计算,这样就只用计算1-n有多少情况了,比较简便。 首先,我们先计算长度小于上界len长度的数的结果(因为长度比上界数小的数一定比上界小,所以1-9可以全部累加,因为不含前导零所以不能从零开始) 接着就是长度和上界一样但是最高位比上界最高位小的数(也可以全部累加)

最后就是关于最高位与上界一样的数的计算了,因为最高位已经确定,因为目前未确定位的高低直接影响了下一位的取值,所以我们必须从高位向下推:len-1到1,枚举从1到上界上该位的值-1(因为如果枚举的数等于上界处该位的数,那么下一位的取值就会受到限制所以不能累加入答案),每一位都这样累加,最后返回累加的结果就计算完毕。

最后的答案是上界下界相减后的结果。

最后上代码

#include<iostream>
#include<cstdio>
#include<math.h>
#include<cstring>
using namespace std;
int f[101][11]={0},n,m,a[11]={0};
void before(){
    for(int i=0;i<=9;i++) f[1][i]=1;
    for(int i=2;i<=32;i++){
        for(int j=0;j<=9;j++){
            for(int k=0;k<=9;k++) if(abs(j-k)>=2) f[i][j]+=f[i-1][k];
        }
    }
}
int solve(int num){
    int ans=0,len=0;
    memset(a,0,sizeof(a));
    while(num) a[++len]=num%10,num/=10; 
    for(int i=1;i<=len-1;i++){ //长度小于len的都可以累加 
        for(int j=1;j<=9;j++) ans+=f[i][j];
    }
    for(int i=1;i<=a[len]-1;i++) ans+=f[len][i];//长度等于len但是最高位小于a[len]的可以累加
    for(int i=len-1;i>=1;i--){
        for(int j=0;j<=a[i]-1;j++){
            if(abs(j-a[i+1])>=2) ans+=f[i][j]; //如果这个数和前一个数不合法则不累加 
        }       
        if(abs(a[i]-a[i+1])<2) break; //如果其中两数的值一定小于2,则没有长度为len最高位相同的情况 
    }
    return ans;
}
int main()
{
    before();
    cin>>n>>m;
    cout<<solve(m+1)-solve(n)<<endl;
}

猜你喜欢

转载自www.cnblogs.com/tianbowen/p/11286422.html