CSU 1993 大司马的三角形中单(数位dp)

1993: 大司马的三角形中单 

      Time Limit: 1 Sec     Memory Limit: 128 Mb     Submitted: 8     Solved: 5    


Description

小T特别喜欢芜湖大司马,小T受到司马老师一手“三角形中单“启发,他给大家出了一道题目:想到在数字中也有类似”三角形”这样规律的数字,叫做“三角形数”。什么是三角形数呢? 就是“一上一下”,用手比划一下有点像三角形。例如:121,132,1210就是三角形数,123,321,1221就不是三角形数。数字满足从左到右每一位数字严格按照先增后减的顺序,这么说你懂我意思了吧! 现在要你求在闭区间[L,R]中有多少个“三角形数”? 如果你觉得题目这么简单那你就错了,我还要稍微折磨你一下,我规定一个数满足先增后减的条件下,这个数乘十还是三角形数,例如:1210,12100,1210000。 如果你没有黄金以上的实力,想搞出这道题,唉!不存在的??

Input

第一行输入一个正整数t,表示t(1< t <= 20)个测试样例。 每一个样例有两个数字:L,R(1<L<=R<=10^12)表示一个区间。

Output

每组样例输出在闭区间[L,R] 中有多少个“三角形数”。

Sample Input

2
1 130
1300 1320

Sample Output

3
3

Hint

Source

湖南省第十三届大学生计算机程序设计竞赛热身赛

 


        当时不够时间写了,但是后来回来了还是写了写,想清楚后,二十分钟左右就过了。

        这里所谓三角形数,就是指数字从第一位开始先严格递增,在严格递减,然后最后有多个0也算是允许的。然后问你在一个区间内,有多少个这样的三角形数。这样的题目很显然就是数位dp。可以考虑,设dp[len][x][tp]表示在数字长度为len,第len位数字为x,tp为bool类型,表示是否已经出现转折点。这里我们先不考虑后面可以有多个0的情况。然后转移的话,要仔细考虑清楚,分几种情况。首先看是否出现过转折点,如果当前状态已经出现过转折点,那么前一位一定是严格比他大的。如果没有出现转折点,那么前一位一定是严格比他小的。注意到,如果当前已经出现过转折点,那么还可以从前一位就是转折点的情况转移过来。按照这个枚举前一位的数字,进行记忆化的数位dp搜索。

        然后数位dp一般有一个lim,即表示是否收到区间端点的大小限制。这个我之前讲数位dp的时候没有细细研究,导致我一开始还把拆分后每一位的数字给先反了过来。其实这个是不用的。因为数位dp本身就是相当于先把区间端点翻转过来去求,不然lim起不到合理限制的作用。好吧,我知道自己讲得有点玄学。。。

       这个时候,我们就可以考虑可以有多个0的情况。很显然,我可以特判一下如果当前位置已经是0了,那么前一位的最大限制不是-1而是0。这样就可以解决0的问题,具体见代码吧:

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

long long dp[13][10][2],num[13];

long long dfs(int len,int x,bool tp,bool lim)
{
    if (len==1) if (tp) return 0; else return 1LL;			//如果len为1,而且居然还是出现了转折点,那么显然转折点是其本身
    if (!lim&&dp[len][x][tp]>-1) return dp[len][x][tp];			//而这显然不合法故返回0,否则返回1有解
    long long res=0;
    if (tp)
    {
        int up=lim?num[len-1]:9;
        for(int i=x+1;i<=up;i++)
            res+=dfs(len-1,i,1,lim&&i==up);
        if (len>2)							//设前一位是转折点的情况,如此len-1必须大于1
            for(int i=x+1;i<=up;i++)
                res+=dfs(len-1,i,0,lim&&i==up);
    } else
    {
        long long up1=x==0?0:x-1;					//如果当前位置已经是0,那么上届是0而不是-1
        int up=lim?min(num[len-1],up1):up1;
        for(int i=0;i<=up;i++)
            res+=dfs(len-1,i,0,lim&&i==num[len-1]);
    }
    if (!lim) dp[len][x][tp]=res;
    return res;
}

long long cal(long long n)
{
    long long res=0,len=0;
    memset(dp,-1,sizeof(dp));
    while(n)
    {
        num[++len]=n%10;
        n/=10;
    }
    for(int i=3;i<=len;i++)					//枚举长度
    {
        int up=i==len?num[i]:9;
        for(int j=1;j<=up;j++)					//枚举第一位放的数字
            res+=dfs(i,j,1,i==len&&j==up);
    }
    return res;
}

int main()
{
    long long n,m;
    int T_T; scanf("%d",&T_T);
    while(T_T--)
    {
        scanf("%lld%lld",&n,&m);
        printf("%lld\n",cal(m)-cal(n-1));
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值