OTTFFSSEN

问题 E: OTTFFSSEN

时间限制: 1 Sec   内存限制: 512 MB
提交: 91   解决: 34
[ 提交][ 状态][ 讨论版]

题目描述

AFO已久的Aglove重新开始学数数,他励志要成为数数少年。为了避免爆零的尴尬,他决定对零这个万恶的数字视而不见。
他的老师为了调教他,就给他出了一道题:
求在区间[L,R]范围内的正整数中在十进制表示下不是0的数位的个数。
Aglove已经AFO太久太久了,况且他还要陪妹子数星星,他决定将这个光荣而伟大的任务委任给你,如果你能顺利完成,你将成为伟大的数数少年。

输入

第一行有两个整数L,R,意义如题目所述。

输出

输出题目要求的结果。

样例输入

23 233

样例输出

515

OTTFFSSEN

这道题不得不吐槽一下,我用数位DP活活杠了一个下午加上午的一个小时。终于打完了。

这道题貌似打法很多。可以打表,可以模拟,还可以数位DP。考试的时候就留了10分钟打这道题,于是乎打了最暴力的暴力,就水过了最基本的30%。剩下的30%是打表,20%是数位DP,20%貌似是,还有要加上高精度。

数位DP光看就看了好几个小时,终于弄懂了一点,基本就可以理解为从i-1位转移到i位,中间加上多加的那一位所改变的量即可。主要麻烦在怎么对非10^k次方数位进行处理上。这道题我设定的状态数组为f[i][j],共i位且第i为为j,允许前缀0。例如f[5][1]代表100000~199999,f[5][0]代表0~9999。那么转移也很清晰了:

f[i][0]=f[i-1][0~9],f[i][1]=f[i-1][0~9]+xp[i-1],2~9类推,xp[i]为10的i次方。

之后就是本题最大的难点了(在我看来),对零散数据即非10^k的处理,活活调了半个下午,第一次用的深搜,挂的没商量,然后听了王旭的,找了本题的原型:BZOJ1833。找到了一个模板,但该题与本题最大的不同即为该题所找的为0~9各个数的出现的次数,因此状态数组多一维,而且不允许前缀0。因此该题标程并不完全适合本题,因此在纠结了好几个小时后将之改为符合本题的处理方式。

首先先预处理出来该数的长度和各位的数字。然后ans=0,ans+=f[len][0~a[len]-1]。然后进行零散处理。

从len-1开始依次递减处理,假设该数为6524,则第一次处理为0~5999,第二次为6000~6499,第三次为6500~6519,第四次为6520~6524,处理的范围依次缩小,最终精确到个位(实际上是0位,因为根据本算法,若为一位数则无法向下精确)。求出本次精确位数内有几个零很简单,只要假设该数本来只有那几位就好了,然而这样下来会WA,原因很简单,没有“补位”。所谓“补位”,顾名思义,将之前求时少加的位加上,那么一次加多少位呢,len-i吗,不是。因为如120052156,就不用补那么多位,因此需要设定一个变量,记录到当前为为止有多少个非0位那么补多少个呢,补本次处理所覆盖的数字数,也就是(a[l-1]*xp[i-1])。做到这里就完成80%了,接下来是记录该数字最后一位非零数在哪,如200,用到目前为止的算法只能算到199,因此需要处理最后一位,只要将ans+=js即可,也正因如此,js++的条件为a[i+1]!=0且这个语句一定要放在la语句之前。核心代码就是这些,不到30行却坑了我一个下午,询问是只要求[0,r]-[0,l-1]的差即可。

虽然核心代码只有30几行,但坑了我一下午,在此鸣谢刘旭阳给我用来测试的程序和王旭给我的原型题!!!


#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#define V 10005
using namespace std;
int n,k;
int ans,a[V];
int s[855][6555];
inline void update( int x, int y, int z)
{
    for ( int i=x;i<=k;i+=i&(-i))
     for ( int j=y;j<=5555;j+=j&(-j))
     s[i][j]=max(s[i][j],z);      
}
int GG( int x, int y)
{
     int sk=0;
   for ( int i=x;i>=2;i-=i&(-i))   
    for ( int j=y;j>=2;j-=j&(-j))
    sk=max(sk,s[i][j]);
    return sk;
}
int main()
{
  //freopen("in.txt","r",stdin); freopen("out.txt","w",stdout);
  //freopen("wbtree.in","r",stdin); freopen("wbtree.out","w",stdout);
  cin>>n>>k;
  for ( int i=1;i<=n;i++)
  scanf ( "%d" ,&a[i]);
  //cin>>a[i];
  k++;k++;
  for ( int j=2;j<=k;j++)
  update(j,a[1]+j,1);
  int t;
  for ( int i=2;i<=n;i++)
  {
         for ( int j=k;j>=2;j--)
          {
                t=GG(j,a[i]+j)+1;       
                ans=max(ans,t);
                update(j,a[i]+j,t);
          }
  }
  cout<<ans<<endl;
  //while(1);
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值