Codeforces D. Little Elephant and Interval(思维找规律数位dp)

题目描述:

Little Elephant and Interval

time limit per test

2 seconds

memory limit per test

256 megabytes

input

standard input

output

standard output

The Little Elephant very much loves sums on intervals.

This time he has a pair of integers l and r (l ≤ r). The Little Elephant has to find the number of such integers x (l ≤ x ≤ r), that the first digit of integer x equals the last one (in decimal notation). For example, such numbers as 101, 477474 or 9 will be included in the answer and 47, 253 or 1020 will not.

Help him and count the number of described numbers x for a given pair l and r.

Input

The single line contains a pair of integers l and r (1 ≤ l ≤ r ≤ 1018) — the boundaries of the interval.

Please, do not use the %lld specifier to read or write 64-bit integers in С++. It is preferred to use cin, cout streams or the %I64d specifier.

Output

On a single line print a single integer — the answer to the problem.

Examples

Input

Copy

2 47

Output

Copy

12

Input

Copy

47 1024

Output

Copy

98

Note

In the first sample the answer includes integers 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44.

思路:

题目是说给一个区间,求这个闭区间内有多少个第一位等于最后一位的数。刚开始,由于数可能会比较大,想到了字符串表示,有找到了一些规律如下:

1---------9---------1*9

11--------99--------1*9

101---------999------10*9

1001-----------9999------100*9

....

这样就先确定区间端点所在的位置,位置中间的整段可以直接求出来。关键在于判断,怎么判断区间端点在整段里前面有多少个数,后面有多少个数。先看只有一个数字的数,然后两个及以上数字的数,先看第一位的元素个数res,再看剩下的位数组成数字的大小tmp和最后一位s[len-1]的大小,如果小于这一部分tmp就为0,大于等于就加上tmp-s[0],再看s[0]与s[len-1]的大小,如果s[0]>s[len-1]就说明包含不了这个最后的数(比如2021,1<2,所以包含不了2022),tmp--,否则tmp就不用减,返回res+tmp。由于这种方法思路复杂,需要很多特判,考虑容易不全,一旦思路转到这个上面,写不对就完了。

字符模拟代码:

#include <iostream>
#include <string>
using namespace std;
string l,r;
long long len1;
long long len2;
long long ans = 0;
long long q_mod(long long a,int b)
{
    long long res = 1;
    if(b==-1)
    {
        return 1;
    }
    while(b)
    {
        if(b&1) res = res*a;
        a = a*a;
        b>>=1;
    }
    return res;
}
long long sub(string s)
{
    //cout << "s " << s << endl;
    int len = s.size();
    if(len==1)
    {
        return s[0]-'0';
    }
    long long res = 0;
    res = (s[0]-'1')*q_mod(10,len-2);
    long long tmp = 0;
    for(int i = 1;i<len;i++)
    {
        tmp = 10*tmp+s[i]-'0';
    }
    //cout << "tmp " << tmp << endl;
    if(tmp>=s[0]-'0')
    {
        tmp-=(s[len-1]-'0');
        tmp /= 10;
        if(s[len-1]>=s[0])
        {
            tmp+=1;
        }
    }
    else
    {
        tmp = 0;
    }
    return res+tmp;
}
int main()
{
    cin >> l >> r;
    len1 = l.size();
    len2 = r.size();
    for(int i = len1+1;i<len2;i++)
    {
        ans += 9*q_mod(10,i-2);
    }
    long long tmp1 = sub(l);
    if(l[0]==l[len1-1]) tmp1--;
    long long tmp2 = sub(r);
    //cout << "tmp1 " << tmp1 << " tmp2 " << tmp2 << endl;
    //cout << "q_mod " << 9*q_mod(10,len1-2) << endl;
    if(len1==len2)
    {
        ans += tmp2-tmp1;
    }
    else
    {
        ans += (9*q_mod(10,len1-2)-tmp1)+tmp2;
    }
    cout << ans << endl;
    return 0;
}

当然这题可以有跟简单的考虑。我们要求[l,r]的满足条件的数,不如求[1,l-1],[1,r]的数,最后两个区间相减。同时也不用字符串,直接找规律,直接将数除以10,就得到它在[11,n]中满足条件的数。比如1024除以10就是102,表示[11,1024]里有102个满足条件的数。再加上9就得到[1,1024]的数。

至于为什么会这样,我们依据上面的规律

1---------9---------1*9

11--------99--------1*9

101---------999------10*9

1001-----------9999------100*9

....

发现[11,n]中满足条件的数是\(num = 10^{len(n)-1}-1\),如果n=99...9的话,其中len(n)是n的长度。我们可以发现99999/10=9999,实际上就是\(num=10^4-1\)。其实一个数满足首末元素相等的话,我们除以十就忽略掉了末元素。实际上除以十就是上一种思路的简化表示。上一种思路中的res与tmp部分做的就是这个事。举个栗子:1024,1024/10=102,这102是怎么来的呢:999/10+2-0+1,999/10表示前面整段满足条件的个数,2-0+1做的就是上一种思路做的。

所以这一点后,实现就简单了,思路也简单了。注意的是一位数的特判,直接返回即可。

找规律代码:

#include <iostream>
using namespace std;
long long l,r;
long long cal(long long n)
{
    long long ans = n/10;
    if(n<10) return n;
    ans += 9;
    int tmp = n%10;
    while(n>=10)
    {
        n /= 10;
    }
    if(n>tmp)
    {
        ans--;
    }
    return ans;
}
int main()
{
    cin >> l;
    cin >> r;
    cout << cal(r)-cal(l-1) << endl;
    return 0;
}

然后,这道题是跟数的组成有关的,因此可以考虑数位dp,求出[1,l-1]与[1,r]的满足条件的数,相减即可,思路与上一种类似。

dp[pos] [num]表示处理到第pos位,第一位是num的满足条件的数的个数。代码中st表示前一位数,lead表示有无前导零,limit表示对当前位有无限制。注意数据范围。

数位dp代码:

#include <iostream>
#include <memory.h>
using namespace std;
long long dp[20][10];
int x[20];
int cnt;
long long dfs(int pos,int num,int st,int limit,int lead)
{
    if(pos<1) return num==st;
    if(dp[pos][num]!=-1&&!limit&&!lead) return dp[pos][num];//记忆化搜索
    int up = limit?x[pos]:9;
    long long res = 0;
    for(int i = 0;i<=up;i++)
    {
        if(lead&&(i==0))//若有前导零,这一位也是零,继续处理下一位
        {
            res += dfs(pos-1,0,st,i==up&&limit,lead);
        }
        if(lead&&i)//若有前导零,这一位不是零,以这一位为数的第一位
        {
            if(1==pos) st = i;//如果这是数的唯一一位,也是最后一位,把st设为i
            res += dfs(pos-1,i,st,limit&&i==up,!lead);
        }
        if(!lead)//若无前导零,正常递归
        {
            res += dfs(pos-1,num,i,limit&&i==up,lead);
        }
    }
    if(!limit&&!lead) dp[pos][num] = res;//记录
    return res;
}
long long Count(long long n)
{
    cnt = 0;
    while(n)
    {
        x[++cnt] = n%10;
        n/=10;
    }
    /*for(int i = 1;i<=cnt;i++)
    {
        cout << x[i] << " ";
    }
    cout << endl;*/
    return dfs(cnt,0,-1,1,1);
}
int main()
{
    long long l,r;
    cin >> l >> r;
    memset(dp,-1,sizeof(dp));
    //cout << "rr " << Count(r) << endl;
    //cout << "ll " << Count(l-1) << endl;
    cout << Count(r)-Count(l-1) << endl;
}

参考文章:

luminous11,CodeForces 204A Little Elephant and Interval,https://blog.csdn.net/luminous11/article/details/43971413

My_ACM_Dream,codeforces 204A Little Elephant and Interval (数位dp),https://blog.csdn.net/my_acm_dream/article/details/43373419

Mathison,数字组成的奥妙——数位dp,https://www.luogu.org/blog/virus2017/shuweidp

wust_wenhao,数位dp总结 之 从入门到模板,https://blog.csdn.net/wust_zzwh/article/details/52100392

posted @ 2019-08-06 11:10  小张人  阅读(340)  评论(0编辑  收藏  举报
分享到: