gdut Problem G: 等凹数字 (数位dp)

96 篇文章 0 订阅
64 篇文章 0 订阅


Problem G: 等凹数字

Description

定义一种数字称为等凹数字,即从高位到地位,每一位的数字先非递增再非递减,不能全部数字一样,且该数是一个回文数,即从左读到右与从右读到左是一样的,仅形成一个等凹峰,如5432123455544334455是合法的等凹数字,543212346123321,111111不是等凹数字。现在问你[L,R]中有多少等凹数字呢?

Input

第一行一个整数T,表示数据的组数。

接下来T行每行俩个数字L和R,(1<=L<=R<=1e18)

Output

 输出一个整数,代表[L,R]中有多少等凹数字

Sample Input

21 100101 200

Sample Output

01

HINT

 小于等于2位的数字无凹峰


解题思路:

一道也许对于大佬来说是稍难的数位dp题, 但对我来说是算很难了。

一开始想的是把数拆成一半,去搜索前一半,记录是否递减的状态,然后在把另一半加上去判断是否大于上届,但是这样就不能记忆化了, gg。

这题有三个状态记录,是否出现递减,是否出现递增, 是否是回文串,前两个状态好说,判断回文串的时候我们需要用一个数组记录自己枚举的数,然后当pos小于len/2的时候,每一位都去比较下。但是问题来了,len是不确定的,并不是一开始那个len,如果想到怎么求len就好做了,为什么呢,因为去搜索的时候会出现一些签到零,知道为什么其实也不难了,我们再加一个状态记录当前位是否是前导零,如果是,每次把len-1,当出现不是前导零的数的时候,len就固定不变了,这样就可以做了。

代码实现起来还是比较麻烦的,许多细节需要注意,比如len的初始值要协调好len/2和pos的比较,判断回文串的时候i和对称位(需要用len计算)的数的比较。好久没写数位dp,以及自己手残,码了好久才出来,


代码:

#include <bits/stdc++.h>
using namespace std;
int num[22];
int dnum[22];
long long dp[20][20][11][2][2][2][2];
int cal(long long x)
{
    int i=0;
    while(x>0)
    {
        num[i++]=x%10;
        x/=10;
    }
    return i;
}
long long  dfs(int pos, bool ismax, int len, int pre, bool up, bool down, bool ok, bool pz)
{
    if(!ok)return 0;
    long long ret=0;
    if(pos<0)
    {
        if(down && up && ok) return 1;
        else return 0;
    }

    if(!ismax && dp[pos][len][pre][up][down][ok][pz]>=0)
    {
        return dp[pos][len][pre][up][down][ok][pz];
    }
    int i, j;
    int bnd=ismax?num[pos]:9;
    for(i=0; i<=bnd; i++)
    {
        dnum[pos]=i;
        if(pz){ret+=dfs(pos-1, ismax && i==num[pos], len-(pz&&i==0), i, up, down, ok, pz&&i==0);continue;} //仍然处于前导零位,继续递归求len
        if(pre==i)
        {
            if(ok && pos<len/2) //当pos小于len/2的时候说明到了数的后半段了,需要去判断是否满足回文串
            {
                ret+=dfs(pos-1, ismax && i==num[pos], len, i, up, down, ok&&dnum[len-pos-1]==i, pz); 
            }
            else ret+=dfs(pos-1, ismax && i==num[pos], len, i, up, down, ok, pz);
        }
        else if(pre<i)
        {
            if(!down)continue;  //递增必须出现在递减出现之后

            if(ok && pos<len/2)
            {
                ret+=dfs(pos-1, ismax && i==num[pos], len, i, 1, down, ok&&dnum[len-pos-1]==i, pz);
            }
            else ret+=dfs(pos-1, ismax && i==num[pos], len, i, 1, down, ok, pz);
        }
        else if(pre>i)
        {
            if(up)continue;
            if(ok && pos<len/2)ret+=dfs(pos-1, ismax && i==num[pos], len, i, 0, 1, ok && dnum[len-pos-1]==i, pz);
            else ret+=dfs(pos-1,  ismax && i==num[pos], len, i, 0, 1, ok, pz); 
        }
    }
    if(!ismax)return dp[pos][len][pre][up][down][ok][pz]=ret;
    else return ret;
}
int main()
{
    memset(dp, -1, sizeof dp);
    int t, i, j, x, y;
    long long  l, r, ll, rr; 
    cin>>t;
    while(t--)
    {
        scanf("%lld%lld", &l, &r);
        int len=cal(l-1);
        ll=dfs(len-1, 1, len, 0, 0, 0, 1, 1);
        len=cal(r);
        rr=dfs(len-1, 1, len, 0, 0, 0, 1, 1);
        printf("%lld\n", rr-ll);
    }
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值