2024 ICPC 陕西省赛 G题 Disappearing Number 非标准答案,找规律+纯粹的数学计算AC

G题链接

文字题解:

        1. 找下规律,当我们取k=7时,我们可以知道 0-9之间有1个,10到19有一个,以此类推,

但0到99则不是,我们可以将这100个数分为10个大小为10的区间,并且要在70到79中特殊处理,

可以得到0到99中有1*10 - 1 +10 =19个,10个区间各有,一个,有一个区间特殊处理,所以先减去原来加上的1个,再把那10个加上去,即为19个。

        同样的,我们可以把上面的规律推广到更大的位数上,比如,0到999,我们可以先将这分成10个部分,每个部分为大小100的区间,并且要在700到799中特殊处理,即为19*10 - 19+100;

        由此,我们可以得到公式,设a[i]的值为0到位数为i的最大正整数的消失的数字的个数,a[1]=1, a[i]=a[i-1]*10 -a[i-1] + pow(10,i-1)   ,pow为求10的i-1次方。那么我们就很轻松的可以得到,0到9,0到99,0到999,甚至十几二十位的个数。

        2. 但是只有这些是不够的,我们还需要更多的题目理解。

                假设,n为123456,那么,123456之前消失的数字为100000之前的加上20000之前的加上3000之前的加上400之前的加上50之前的加上6之前的消失的数字,总和即为123456之前消失的数字个数。其实这个乍一看挺难理解,但是我们可以这样想,比如,120这个数,你先到100,肯定是不够的,因为你在从100到120的时候,这两个数之间,肯定有更多的消失的数字,那这些数字的个数,不就恰好相当于0到20之间的消失的数字的个数吗?所以这个猜想非常简洁,但有效。

        3. 但是,我们不难发现,上面的2个结论乍一看有问题,因为,我们在第一个的结论中,只找到了固定的数字的结论,比如说,题目给的n,不可能那么简单的,给9,99,999,9999这样简单的数字,所以我们还要继续,以下是规律的推导:

                比如说,n= 170,  那,我们循环遍历,遍历到70时,我们该怎么看它前面有多少个数呢?       

                我们需要分类讨论:

                一,当k=8,即为k>7时,那么,70前面,就不存在,80到89这样特殊的数字,并且,70并不包含8,我们可以直接算69前面有多少个消失的数字,将69分为7个大小为10的区间,每个区间的消失的数字个数即为a[1],也就是1个,答案即为ans+= 7;

                二,当k=7,即为k==7时,那么同样的,不就相当于,第一种情况,加个一吗,因为,69前面有7个,而70又为一个,不就是多了一个70这个消失的数吗,ans+= 7+1;

               三,当k=6,即为k<7时,就要处理特殊情况了,69前面有60到69这10个数字,所以,ans+= 7-1+10, 先减去原来算上的,再加上这个特殊的区间的个数,因为7是包含60到69的67这个数的,我们的公式得出的a[i]是这样的,所以才需要减一个a[i]。

        

        经过上面三个极其艰难(博主比较弱)的思考过程之后,我们就可以得出代码了,输入n,k后,遍历n的每一位上的数,根据那一位的数跟k的大小比较,以及,该位是第几位,要选择哪个a【i】,再全部相加,即为,该n前面消失的数字的个数。因为题目是从0开始的,所以答案要加1。

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const ll maxn = 2e6+5;
ll n,k;
ll a[20];//含义如题解中的a[i]

ll jisuan(ll x,ll y){
    ll res = 1;
    for(int i = 1;i<=y;i++){
        res*=x;
    }
    return res;
}//求次方的函数,类似于pow

ll swei(ll x){
    ll res = 0;
    while(x){
        res++;
        x/=10;
    }
    return res;
}//求位数的函数


int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    a[1]= 1;
    for(int i = 2;i<= 19;i++){
        ll j =i-1;
        ll num = jisuan(10,j);
        a[i] = a[i-1] *9 +num;
    }//用公式,把a【i】求出来

    cin>>n;
    string str;
    ll x;
    while(n--){
        cin>>x>>k;
        ll res = 0;
        str = to_string(x);

        for(int i = 0;i<str.size();i++){//遍历数的每一位
            int num = str[i]-'0';
            int wei = str.size()-i;
            wei-=1;//根据位数,来决定下面的a[i],
                    //不过需要注意的是,wei要--;因为,比如算70,70是两位数,但,要用a[1]的值
                    //可以理解为,a[1]代表的是,区间长度为pow(10,1)之间的个数,a[2]就代表区间长度为pow(10,2)之间的个数。


            if (num < k) {//比较大小
                res += a[wei] * num;
            }
            else if (num == k)res += a[wei] * num + 1;
            else res += a[wei]*(num-1)+ jisuan(10,wei);
        }
        cout<<x+1-res<<endl;//答案要+1;
    }
    return 0;
}


因为,我看到标准答案,跟其他的题解,都是标准的用进制的思想做的,但是博主本人比较弱,只能暴力用数学计算,找规律,但是觉得挺好玩的,就发发看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值