题目
样例输入
5
1
3
20
38
56
样例输出
1
2
5
2
0
思路
首先,我们来观察一下数据范围:k的上限是一个1e18,暴力枚举显然过不了(甚至都算不出来)......
其中,test()函数是暴力枚举的算法,通过枚举找到当前枚举到的数字,再通过除法与取模操作来计算第k位的数字。这个函数是测试优化算法答案是否正确的,不多做说明。
优化算法:
将k为前面的数字分层,1~1到1~9为一层、1~10到1~99为一层,依次类推,每次最高位数位数加1分一层。
首先进行初始化,计算b[ i ]、c[ i ]、d[ i ]。mul[ i ]是一个乘数的数字(因为感觉每一次求幂次方太慢了,有mul数组相当于只求一次幂次方),其中:
b[ i ]是每一层最后一个数的k值,c是每一层第一个数(不是每一位的数字)最后一位的k值,d是每一层中最后一组的位数。
num()函数中:
通过b数组先对k进行分层,判断最大层数。
然后进入mid()函数,判断k位具体处在哪一组(求出1~ i 中 i 的值)。
在二分的时候,将k值减去前一层的值,求出一个相对k值。然后通过c数组求出每一组的第一个位数与最后一个位数,进行二分。最后得出的结果是k值所在组的第一个位数(返回时需要加上偏移量)。
进入ans()函数:
通过c数组求出k的大致范围u~v(其中u与v之间相差一个数量级)。然后通过除法操作求出k所在的具体数字,再通过除法操作与取模操作求出k所在位数的数字。
总之,该方法就是一个不断分层次的算法,其中一部分通过二分进行优化。
代码
#include<iostream>
#include<cmath>
using namespace std;
long long a[17] = { 9,180,2700,36000,450000,5400000,63000000,720000000,8100000000,90000000000,990000000000,
10800000000000,117000000000000,1260000000000000,13500000000000000,144000000000000000,1530000000000000000 };
long long mul[10] = { 0 };
long long b[10] = { 0 };//每一层结束的个数
long long c[10] = { 0 };//每一层第一个的个数
long long d[10] = { 0 };//每一层最后一组个数
void init()
{
mul[0] = 9;
b[0] = 45;
c[0] = 1;
d[0] = 9;
for (int i = 1; i < 10; i++)
{
mul[i] = mul[i-1] * 10;
d[i] += (d[i - 1] + a[i]);
b[i] = (i + 1)*(1 + mul[i]) * mul[i] / 2 + b[i - 1];
for (int j = 0; j < i; j++)
b[i] += mul[i] * a[j];
c[i] = c[i - 1] + a[i - 1] + 1;
cout << "d[i]=" << d[i] << endl;
}
}
long long mid(int rankk, long long begin, long long k)
{
long long now = k - begin + 1;
long long left = pow(10, rankk) - 1;
long long ll = pow(10, rankk) - 1;
long long mid = 5 * pow(10, rankk);
long long right = pow(10, rankk + 1) - 1;
long long mid1 = (mid - left - 1) * (2 * c[rankk] + (rankk + 1)*(mid -ll - 2)) / 2;
long long mid2 = (mid - left) * (2 * c[rankk] + (rankk + 1)*(mid - ll - 1)) / 2;
long long beore = mid1;
while (1)
{
if (now > mid1&&now <= mid2)
return mid1 + begin;
else if (now <= mid1)
{
right = mid;
mid = (mid + left + 1) / 2;
mid1 = (mid - ll - 1) * (2 * c[rankk] + (rankk + 1)*(mid - ll - 2)) / 2;
mid2 = (mid - ll) * (2 * c[rankk] + (rankk + 1)*(mid - ll - 1)) / 2;
}
else
{
left = mid;
mid = (right + left + 1) / 2;
mid1 = (mid - ll - 1) * (2 * c[rankk] + (rankk + 1)*(mid - ll - 2))/2;
mid2 = (mid - ll) * (2 * c[rankk] + (rankk + 1)*(mid - ll - 1))/2;
}
}
}
long long num(long long k)
{
int i = 0;
long long begin;
if (k <= b[0])
begin = 0;
else
{
for (i = 1; i < 11; i++)
{
if (k < b[i])
{
begin = b[i - 1];
break;
}
}
}
long long j = 0;
begin++;
long long before = begin;
return mid(i,begin,k);
}
long long ans(long long k, long long now)
{
now = k - now + 1;
int rankk = 0;
if (now <= 9) return now;
for (int i = 2; i < 10; i++)
{
if (now <= c[i] - i - 1)
{
rankk = i;
break;
}
}
long long x = c[rankk - 1] - rankk;
long long remain = now -x;
long long y = remain / rankk + mul[rankk-1] / 9;
long long yu = remain % rankk;
long long v = mul[rankk - yu] / 9;//取模操作
if (yu == 0) return (y - 1) % 10;
else return (y / v) % 10;
}
int test(long long k)
{
long long c;
long long com;
long long pc = 0;
for (long long i = 1; pc <= k;i++)
{
for (long long j = 1; j <= i; j++)
{
if (j < 10)
{
c = j;
pc++;
if (pc == k) return c;
}
else if (j < 100)
{
int ii = 2;
long long v = 10;
while (ii>0)
{
c = (j / v) % 10;
v /= 10;
pc++;
ii--;
if (pc == k) return c;
}
}
else if (j < 1000)
{
int ii = 3;
long long v = 100;
while (ii > 0)
{
c = (j / v) % 10;
v /= 10;
pc++;
ii--;
if (pc == k) return c;
}
}
else if (j < 10000)
{
int ii = 4;
long long v = 1000;
while (ii > 0)
{
c = (j / v) % 10;
v /= 10;
pc++;
ii--;
if (pc == k) return c;
}
}
else if (j < 100000)
{
int ii = 5;
long long v = 10000;
while (ii > 0)
{
c = (j / v) % 10;
v /= 10;
pc++;
ii--;
if (pc == k) return c;
}
}
else if (j < 1000000)
{
int ii = 6;
long long v = 100000;
while (ii > 0)
{
c = (j / v) % 10;
v /= 10;
pc++;
ii--;
if (pc == k) return c;
}
}
else if (j < 10000000)
{
int ii = 7;
long long v = 1000000;
while (ii > 0)
{
c = (j / v) % 10;
v /= 10;
pc++;
ii--;
if (pc == k) return c;
}
}
else if (j < 100000000)
{
int ii = 8;
long long v = 10000000;
while (ii > 0)
{
c = (j / v) % 10;
v /= 10;
pc++;
ii--;
if (pc == k) return c;
}
}
else if (j < 1000000000)
{
int ii = 9;
long long v = 100000000;
while (ii > 0)
{
c = (j / v) % 10;
v /= 10;
pc++;
ii--;
if (pc == k) return c;
}
}
}
}
}
int main()
{
int n;
cin >> n;
init();
for (int i = 0; i < n; i++)
{
long long k;
cin >> k;
long long j = 1;
//cout << "ans:";
cout << ans(k, num(k)) << endl;
//cout << "test:";
//cout << test(k) << endl;
}
system("Pause");
}
总结
这个优化算法前前后后写了三天,对码力不足的我来说几个小时之内是没办法完成的。所以这个故事告诉我们,不会骗分的程序猿(程序媛)不是好SDUer...