[BZOJ 1026] SCOI 2009 windy数 · 动态规划

本文转自@http://blog.csdn.net/ycdfhhc/article/details/43956247

参考了众神犇的题解后,AC了人生第一道数位DP。

友链:http://hzwer.com/3503.html

首先很容易可以想到,设calc(X)表示1..X闭区间内的windy数,那么答案肯定是calc(B)-calc(A-1)

那么剩下就是如何计算calc(X)的问题了。

预处理DP很水:f[i][j]表示i位的数,最高位数值为j的情况下有多少个windy数,那么递推式:f[i][j]+=f[i-1][k] | k=0..9 & Abs(j-k)>=2

这题的关键是分解数据,笔者做的时候还是个蒟蒻,所以碰到一些问题一下子没搞懂,所以写在这里:比如f[3][4],表示4开头的三位数有多少个windy数,实际范围是400…499。

笔者做题的时候对对数字拆解还不怎么理解,所以在此也以2682这个数写个栗子:

首先,拆成0..999,1000…1999,2000..2999,此时2999已经超过了2682,所以对2000…2682还要细分,此时我们可以把开头的2拿掉,又变成了0…682,和2000…2682是一个效果。那么0..682再向上述过程一样拆解。

如1000…1999这个值怎么算呢?这不就是f[4][1]吗?但是注意,由于不能有前导0,所以0..999的windy数并不等于f[4][0],而是0..99,100..199······900..999 同上述方法一样处理,这个操作虽然讲解起来有点繁琐,但实际代码很简单(line26~29)

 

还有另外两个注意点,坑的笔者多交了两次:

1、0要特判。(否则WA的很惨还不明不白的。。。)

2、当处理到某一位的原本值已经不符合要求的时候,直接Break,否则会多计算。还是举个栗子吧:1457,在做到第3位的时候,5和前面的4已经冲突,所以接下来再处理的145..开头的数肯定都是不符合要求的,再加上去就是多算了。

  1. #include <stdio.h>  
  2. #include <algorithm>  
  3. #include <string.h>  
  4. #include <iostream>  
  5. using namespace std;  
  6. #define ll long long  
  7.   
  8. ll a,b,f[11][10],d[11];  
  9.   
  10. void init(){  
  11.     cin>>a>>b;  
  12.     memset(f,0,sizeof f);  
  13.     for (int i=0;i<=9;i++)  
  14.         f[1][i]=1;    
  15.     for (int i=2;i<=10;i++)  
  16.         for (int j=0;j<=9;j++)  
  17.             for (int k=0;k<=9;k++)  
  18.                 if (abs(j-k)>1) f[i][j]+=f[i-1][k];  
  19. }  
  20.   
  21. ll calc(int x){  
  22.     if (!x) return 0;   //0特判   
  23.     int ans,n;  
  24.     ans=n=0;  
  25.     while (x) d[++n]=x%10,x/=10;  
  26.     for (int i=1;i<n;i++)  
  27.         for (int j=1;j<=9;j++)  
  28.             ans+=f[i][j];  
  29.     for (int i=1;i<d[n];i++) ans+=f[n][i];  
  30.     for (int i=n-1;i>0;i–){  
  31.         for (int j=0;j<d[i];j++)  
  32.             if (abs(d[i+1]-j)>1) ans+=f[i][j];  
  33.         if (abs(d[i]-d[i+1])<=1) break;      //原数有冲突时直接退出   
  34.     }  
  35.     int can=1;  
  36.     for (int i=2;i<=n;i++)       //特判 x本身这个数   
  37.         if (abs(d[i]-d[i-1])<=1) {can=0;break;}  
  38.     ans+=can;  
  39.     return ans;  
  40. }  
  41.   
  42. int main(){  
  43.     init();  
  44.       
  45.     cout<<(calc(b)-calc(a-1))<<endl;  
  46.       
  47.     return 0;  
  48. }  
 
 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值