码迷,mamicode.com
首页 > Windows程序 > 详细

$P2657\ [SCOI2009]\ windy$数

时间:2019-11-10 22:50:50      阅读:158      评论:0      收藏:0      [点我收藏+]

标签:string   总结   前缀和   表示   return   开始   char   问题:   oid   


属于数位\(DP\)入门级别的题目,但我做这类题不多,还是要总结一下这道经典题目

\(Description\)

题面
给定\(a,b\),求\([a,b]\)区间有多少个数满足:任意两个相邻数位之间的差的绝对值\(>=2\)
\(a,b<=1e12\)

\(Solution\)

数位\(DP\)的基本思想是一个一个数确定,逼近到边界
数位\(DP\)一般设计状态为\(dp[i][s]\)表示当前考虑到第\(i\)位(从最低位编号),当前位置或附近位置状态为\(s\)的方案数。
有时候需要预处理,有时候直接数位\(dp\)即可

对于这个题,比较显然的是设计状态\(dp[i][j]\)表示当前考虑到第\(i\)位(\(1--(i-1)\)都已经考虑),第\(i\)位为\(j\)的方案数
状态转移比较显然,注意也要处理\(0\)的情况

void pre()
{
    pow[0]=1;
    for(re int i=1;i<=13;++i) pow[i]=pow[i-1]*10;//pow[1]=10,i表示1o^i;
    for(re int i=0;i<=9;++i) dp[1][i]=1;//单独处理一位的情况 
    for(re int i=2;i<=12;++i)//注意从2开始 
     for(re int j=0;j<=9;++j)
      for(re int k=0;k<=9;++k)
        if(abs(j-k)>=2) dp[i][j]+=dp[i-1][k];//把0的情况也处理 
}

统计答案时就照着数位\(DP\)的套路统计,不过这道题要注意前导零,比如例子:\(65536\)
技术图片
如此统计还有几个问题:
\(1.\)不能直接统计\(00000-59999\),因为像\(01xxx\)这样的答案会被判断为不合法,但是答案要求不包含前导零,所以这种情况是合法的。所以我们要枚举答案时\(1、2、3、4\)位数的情况,来消除前导\(0\)的影响
\(2.\)注意到每次逼近都是枚举到当前位置的数\(-1\),因为这样后面的位置可以考虑所有情况,所以最后会枚举到\(65535\)而忽略\(65536\),把边界设到\(x+1\)\(65537\)就行了。
\(3.\)注意\(65xxx\)的答案实际上已经不合法了,我们枚举第二高位的时候会排除这种情况,但到了下一位直接默认这一位是\(5\)了,因此就不合法了。所以每次跳到下一位之前要判断这一位和前一位是否合法,不合法直接退出,\(return\),因为后面美剧的都是\(65xxx\),都不合法不用考虑了。
更多细节见代码

\(Code\)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define re register
#define ll long long
using namespace std;
inline ll read()
{
    ll x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
ll a,b;
ll dp[20][20],pow[20],cnt;
void pre()
{
    pow[0]=1;
    for(re int i=1;i<=13;++i) pow[i]=pow[i-1]*10;//pow[1]=10,i表示1o^i;
    for(re int i=0;i<=9;++i) dp[1][i]=1;//单独处理一位的情况 
    for(re int i=2;i<=12;++i)//注意从2开始 
     for(re int j=0;j<=9;++j)
      for(re int k=0;k<=9;++k)
        if(abs(j-k)>=2) dp[i][j]+=dp[i-1][k];//把0的情况也处理 
}
ll ask(ll x)
{
    ll tmp=x,ans=0,last;
    cnt=0;
    while(tmp) {tmp/=10,cnt++;}
    int now=x/pow[cnt-1];//now表示当前考虑的最高位 
    for(re int i=cnt-1;i>=1;--i) 
     for(re int j=1;j<=9;++j)
      ans+=dp[i][j];//因为不包含前导0,所以01这种答案是合法的,枚举每个位置作为起点(1-(cnt-1))位数的开头
      //枚举的值是1-9,因为它是开头不含前导零,最后不用考虑0的情况,因为数据保证a>=1,考虑不考虑都会被前缀和相减消除 
    for(re int i=1;i<now;++i) ans+=dp[cnt][i];//第cnt位要单独处理 
    last=now;
    x%=pow[cnt-1];//last表示上一位,用于枚举下一位是判断 
    for(re int i=cnt-1;i>=1;--i)
    {
        now=x/pow[i-1];//提取最高位 
        for(re int j=0;j<now;++j)
        if(abs(last-j)>=2) ans+=dp[i][j];
        if(abs(now-last)<2) break;//!!!重要:假如这两个值不符合了,后面不用考虑了。
        //比如10765,后面考虑的都是10xxx,一定都不符合了 
        last=now;//更新一下上一位 
        x%=pow[i-1];//注意取模保证每次取到当前的最高位 
        
    }
    //统计的是开区间,边界数不会被统计
    return ans; 
     
}
int main()
{ 
    a=read(),b=read();
    pre();
    ll tmp1=ask(b+1);
    ll tmp2=ask(a);
    printf("%lld\n",tmp1-tmp2);//统计的是开区间,边界数不会被统计
    return 0;
}

$P2657\ [SCOI2009]\ windy$数

标签:string   总结   前缀和   表示   return   开始   char   问题:   oid   

原文地址:https://www.cnblogs.com/Liuz8848/p/11832044.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!