M2-C-咕咕东的奇妙序列

题目
在这里插入图片描述
在这里插入图片描述
思路
我觉得这道题非常难,一开始我完全找不到思路,只能尽量的骗分,后来问了别人才明白怎么做。本题最核心的是两个数组,ret1和ret2。ret2[i]保存的是从1到最大的i位数的子串的位数,做法是ret[i-1]+i位的所有数的位数,比如ret[2],即1-99的位数,就是1-9的位数(ret[i])加上10-99的位数,(99-9)*2([pow(10,i)-pow(10,i-1)]*i)。ret1[i]中保存的的是从开始到i位数的最大数字开始出现时的位数,1-9为45,1-99为9045(方法是用等差数列求和的方式,这也是本题最核心的一个算法之一,另一个是二分法)。方法不好说,举个例子讲一下9045是怎么求的(即ret1[2]),先算上ret1[1],然后剩下的90×9(1-9九位数),然后一个从10,1011,101112…,101112…99的等差数列求和即可。其他的实现我都给了详细的注释,就是一个挺复杂的数学问题,但是易出错。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define inf 1e18
using namespace std;
typedef long long ll; 
ll ret1[10];    
ll ret2[10];   
ll ret(ll t) {  //求从1开始直到t的位数(包括t) 
 ll tt = t;
 ll r1=0;  //求t是几位数,虽然r1不会太大但是要和r2做运算所以还是把它设为ll型 
 while (tt>0) {
  r1++;
  tt/=10;
 }
 ll r2=0;
 for (ll i = 1;i<r1; i++) {
  r2+=(pow(10, i) - pow(10, i - 1)) * i;
 }
 r2+=(t-pow(10,r1-1)+1)*r1;
 return r2;
}
void Query(){
 int q;
 cin>>q;
 for (int i=0;i<q;i++) {
  int k;
  cin>>k;
  int time;
  for (int j = 1; j <= 9; j++) {
   if (ret1[j] >= k) {
    k-= ret1[j-1];
     time=j; 
     break;  //找到k在最高几位数的分组中,time记录位数,并且k现在是从pow(10,j-1)开始的名次 
   }
  }
  //我们以time为2为例,即从10-99 
  ll l = 1;
  ll r = pow(10,time) - pow(10, time-1);  //10到99一共90个二位数 
  ll mid; 
  ll rt=ret(pow(10, time - 1)); //1-10一共多少位 
     ll rnk=0;//记录mid=r+l/2的名次 
   //二分 
  while (l <= r) {
   mid = (l + r) / 2;//第一次是45 
   rnk= mid*rt+mid*(mid-1)*time/2;//45个rt表示从1-10开始的组到1-45开始的组每一个都有1-10,剩下的就是一个等差数列求和乘以位数 
   if (rnk >= k)  r = mid - 1;//mid的名次大了 
   else l = mid + 1;
  }
  //可能在 mid-1,mid,mid+1组中 
  ll t1=(mid-2)*rt+(mid-2)*(mid-3)*time/2;     //mid-1前面的位数       
  ll t2= (mid-1)*rt + (mid-1)*(mid-2)*time/2;  //mid 
  ll t3= mid *rt + (mid-1)*mid*time/2;               //mid+1 
  if(k<=t2){
   k-=t1;    
  }
  else
  {
   if(k<=t3)
   {   
    k-=t2; 
   }
   else
   {
    k-=t3;  
   } 
  }
  for (int j = 1; j <= 9; j++) {
   if (ret2[j] >= k) {
    k -= ret2[j - 1];    //现在只需要从1-mid里面找了,先把低位数的减去这里是把一位数的减去从全部二位数里面找 
    time=j;      
    break;  
   }
  }
  ll m1=k/time;  //有几个time位数 
  ll m2=k%time;  //k在time位数的第几位 
  if (m2==0) {//在最后一位即个位,求除以10的余数即可 
   ll re=pow(10,time-1)+m1-1;
   cout<<re%10<<endl;
  }
  else {
   ll re=pow(10, time- 1) + m1;//还是算出这个数先 
   ll remain=time-m2;//k后面的time位数的位数 
   re=re/pow(10,remain);//相当于把k后面的位数舍去不看,比如145要想取得4,用145/10得到14,求余取个位即可 
   cout<<re%10<<endl;
  }
 }
}
int main() {
 ret2[0]=0;
 for (ll i = 1; i <= 9; i++) {
  ret2[i] = ret2[i - 1] + (pow(10, i)-pow(10, i - 1))*i;
 }
 ret1[0]=0;
 for (ll i = 1; i <= 8; i++) {
  ll n=9*pow(10,i-1);
  ret1[i]=n*(n+1)*i/2+n*ret2[i-1]+ret1[i - 1];//
 }
 ret1[9]=inf;
 Query();
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值