zoukankan      html  css  js  c++  java
  • [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
    ",ans[S][T]);
           }
           return 0;
    }
    View Code
  • 相关阅读:
    php通过某个日期段的周几,获取选中周几对应的日期
    thinkphp 使用paginate分页搜索带参数
    php小程序登录时解密getUserInfo获取openId和unionId等敏感信息
    微信小程序使用wxParse,解决图片显示路径问题
    php对二维数据排序
    微信小程序下拉框之二维数组或对象
    php获取指定月份月初和月末的时间戳
    WPF中实现自定义虚拟容器(实现VirtualizingPanel)
    SQL Server数据库ROW_NUMBER()函数使用详解
    WPF后台设置xaml控件的样式System.Windows.Style
  • 原文地址:https://www.cnblogs.com/Vocanda/p/12794811.html
Copyright © 2011-2022 走看看