[CF1216E] Numerical Sequence hard version
题目
The only difference between the easy and the hard versions is the maximum value of k.
You are given an infinite sequence of form "112123123412345…" which consist of blocks of all consecutive positive integers written one after another. The first block consists of all numbers from 1 to 1, the second one — from 1 to 2, the third one — from 1 to 3, …, the i-th block consists of all numbers from 1 to i.
So the first 56 elements of the sequence are "11212312341234512345612345671234567812345678912345678910". Elements of the sequence are numbered from one. For example, the 1-st element of the sequence is 1, the 3-rd element of the sequence is 2, the 20-th element of the sequence is 5, the 38-th element is 2, the 56-th element of the sequence is 0.
Your task is to answer q independent queries. In the i-th query you are given one integer ki. Calculate the digit at the position ki of the sequence.
Input
The first line of the input contains one integer q (1≤q≤500) — the number of queries.
The i-th of the following q lines contains one integer ki (1≤ki≤1018) — the description of the corresponding query.
Output
Print q lines. In the i-th line print one digit xi (0≤xi≤9) — the answer to the query i, i.e. xi should be equal to the element at the position ki of the sequence.
Examples
Input
5
1
3
20
38
56
Output
1
2
5
2
0
Input
4
2132
506
999999999999999999
1000000000000000000
Output
8
2
4
1
Note
Answers on queries from the first example are described in the problem statement.
分析
我们维护一个前缀和数组,然后去找第n个数字在哪一行,然后减去前边几行的数字数目和就是答案。
比如n=5,发现它在第三行,前两行的和为3,5−3=2所以第五个数是2。 但如果n很大的话,比如这题,就会T掉,首先上述思想是可以肯定的,所以要用更高效的办法 首先是找到在第几行。 一行行枚举效率太低,要用到一个分块的思想,按照末位数字的位数分块,这样在每个块里找,枚举每个块的时间复杂度是一个常数,先找到它在哪一块,然后再利用二分的思想确定所在行,因为这个是具有单调性的,那么怎么找呢? 考虑每一块中有多少个数,用变量last记录前几个块的数字和,len记录当前枚举的位数,根据等差数列的求和公式(a1+an)∗n/2,当前块的第一行的数字和是last+len手摸一下就能得出,最后一行是last+len∗cnt,其中cnt就是该块内一共有多少数字,如果n大于前几块的数字和sum,就直接减去,否则就找到了所在块,然后再二分求出是那一行就行,求的方法就是不断更新l和r判断sum与n的关系,找到后再减去前边几行的和就是第n个数字在这一行第几个。 这时候我们再重复一下上述过程,只不过不是拆分行而是拆分每个数(不是数字),比如12345678910,拆成1 2 3 4 5 6 7 8 9 10,1和0不分开的这种。然后再枚举每个数字的位数,依次减去,从而求出第n个数字所在数的位数,最后再把这个数还原出来取它的那个位数就行。
代码
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int inf = 0x7f7f7f7f; const int maxn = 1010; int K,M,S,T; int v[maxn],cnt,map[maxn][maxn],used[maxn]; int ans[maxn][maxn],dis[maxn][maxn],tmp[maxn][maxn]; void floyd(int c[][maxn],int a[][maxn],int b[maxn][maxn]){ int i,j,k; for(k=0;k<cnt;k++){ for(i=0;i<cnt;i++){ for(j=0;j<cnt;j++){ if(c[v[i]][v[j]]>a[v[i]][v[k]]+b[v[k]][v[j]]) c[v[i]][v[j]]=a[v[i]][v[k]]+b[v[k]][v[j]]; } } } } void copy(int a[][maxn],int b[][maxn]){ int i,j; for(i=0;i<cnt;i++){ for(j=0;j<cnt;j++){ a[v[i]][v[j]]=b[v[i]][v[j]]; b[v[i]][v[j]]=inf; } } } void solve(int k){ while(k){ if(k%2){ floyd(dis,ans,map); copy(ans,dis); } floyd(tmp,map,map); copy(map,tmp); k=k/2; } } int main(){ int i,j; int x,y,val; while(scanf("%d%d%d%d",&K,&M,&S,&T)==4){ for(i=0;i<=1001;i++){ for(j=0;j<=1001;j++){ map[i][j]=inf; ans[i][j]=inf; tmp[i][j]=inf; dis[i][j]=inf; } ans[i][i]=0; } memset(used,0,sizeof(used)); cnt=0; for(i=0;i<M;i++){ scanf("%d%d%d",&val,&x,&y); if(map[x][y]>val){ map[x][y]=val; map[y][x]=map[x][y]; } if(!used[x]){ used[x]=1; v[cnt++]=x; } if(!used[y]){ used[y]=1; v[cnt++]=y; } } solve(K); printf("%d\n",ans[S][T]); } return 0; }