• Week8 CSP-M2 C


    题目描述及输入输出约定:

     

    比赛时的想法:

    10^18的K,确实让人望尘莫及,但是总觉得二分或许能排上用场。

    最初的想法是直接模拟,存储10^6个字符(打表),但是在使用itoa()函数时,我竟然认为数字0会被转换成0(字符串结束符),而不是‘0’,也没手动写转换函数,后来想想真不知道那时候在想什么。

    放弃上述想法后,我又想用前缀和,用sum[i]表示,序列加到i时的总长度,用a[i]表示从12345...i有多长,然后在O(N)内枚举i,虽然到不了10^18,但是当时觉得还行

    也就是这样预处理:

     for(int now=1;now<=100000;now++)
    {
      //a[j]是从1加到j需要占多少位 
      a[now]=a[now-1]+(int)log10((double)now)+1;
      sum[now]=sum[now-1]+a[now];
    }  

    然后,对于输入的K,在sum数组中,二分求K的下界j(第一次大于等于K是sum[j]),那么多出来的就是K-sum[j-1],假设KK=K-sum[j-1];然后在a数组中,二分求KK的下界j,最后判断是j的第几位即可。

    想法确实是这样,但是二分是我才接触的,很生,写了很长时间,边界细节一直不敢确定,结束时也没写完,最后交了一个30分的代码,遗憾离场。

    没写完的版本:

    #include <cstdio>
    #include <iostream>
    #include <string>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const int MAXN=1e5+5;   
    ll a[MAXN],s[MAXN];
    int main()
    {
    	for(int now=1;now<=100000;now++)
    	{
    		//a[j]是从1加到j需要占多少位 
    		a[now]=a[now-1]+(int)log10((double)now)+1;
    		s[now]=s[now-1]+a[now];
    	}
    	int Q; cin>>Q;
    	while(Q--)
    	{
    		//s是递增的,二分K在s中的位置
    		ll k; scanf("%lld",&k);
    		//求下界
    		int num=lower_bound(s+1,s+1+100000,k)-s; //加了从1到num的序列后,长度刚好超过k,超过k的部
    		int D=k-s[num-1]; //加了D个后才到k,下一步要找加到几长度能是D,从a中找
    		int numD=lower_bound(a+1,a+1+100000,D)-a;  //加到numD长度是D或刚超过D
    		//找出是numD的哪一位数字
    		int D2=D-a[numD-1];  //实际上差几个
    		int WS=a[numD]-a[numD-1]; //numD有几位数 , 取NUMD的第D2位数即可,正数第D2位
    		int pp=WS-D2+1;
    		if(pp==0) pp=1;
    		int ans=(numD/(pp*10))%10;
    		cout<<ans<<endl;
    	}
    }

    用等差数列求和降低复杂度:

    如果采用等差数列,则能以常数复杂度求出序列长度,就不必再像上面存储序列的长度,因为等差数列仅在确定的位数内成立,即从i位到第i+1位时,公差D会变化,所以预存每个边界的序列长度(加到不超过10位数,就超过了18次方)。以等差数列为基础,再二分即可。

    具体的过程:求加到了第几位数i、求加到了哪个数certainValue、求加到了1-certainValue中的哪一个数dst、判定是dst的第几位。

    总结:

    关于这道题:

    多做做二分的题,即使是浅显的二分题,主要目的是熟悉。

    对于long long:如果要用long long,则中间数也要用long long,包括中间值tmp,函数参数,函数返回值。

    关于这次模测:

    很失败,思维混乱,不知道在想什么,或许是因为来了几个亲戚,家里比较乱。

    但是,以后,对于每一个思想,都要进行可行性分析,主要是能不能写出来。

    如果感觉有风险,就把保险的部分先写好,如果最后没写完,就把保险代码交上。

    代码:

    #include <cstdio>
    #include <iostream>
    #include <cmath>
    using namespace std;
    typedef long long ll;
    ll sum[50];		//sum[i]表示序列加到 10^i-1 的长度,也就是加完第k位数及其之前的
    ll base[50];   //base[i]表示序列从1到10^i-1的长度
    ll K;
    
    //返回longlong的函数,中间变量也要用longlong 
    long long qpow(long long a,long long b)
    {
    	if(b==0) return 1; 
    	else if( (b&1) == 0)   //b是偶数 
    	{
    		return qpow(a*a,b/2);
    	}
    	else return a*qpow(a*a,b/2);  //即使a是奇数,比如5,5/2=2,也不影响,但是会递归到1/2=0的时候,所以有一个边界条件是b=0;  
    }
    //evaluate sum[]
    long long sumOfSeries(ll begin,ll n,ll d)  //等差数列求和:首项,项数,公差 
    {
    	return begin*n+(n-1)*n*d/2;
    } 
    void evaluteSum()
    {
    	for(int i=1;i<=10;i++)
    	{
    		ll tot=9*qpow(10,i-1);  	//tot表示i位数一共有多少个,比如2位数有90个,3位数有900个
    		base[i]=base[i-1]+tot*i;
    		sum[i]=sum[i-1]+sumOfSeries(base[i-1]+i,tot,i);
    	}
    }
    ll LowerBound(ll tot,int i) //求下界,找第一个大于等于的 
    {
    	ll L=1,R=tot;	//第i位数一共有tot个
    	ll certain=0; 
    	while(L<=R)
    	{
    		ll Mid=(L+R)/2;
    		ll now=sumOfSeries(base[i-1]+i,Mid,i);
    		if(now>=K) certain=Mid,R=Mid-1;
    		else if(now<K) L=Mid+1; 
    	}
    	return certain;
    }
    
    int main()
    {
    	evaluteSum();
    	int Q; cin>>Q;
    	while(Q--)
    	{
    		cin>>K;
    	//	int len=(int)log10((double)K)+1;
    		int i=1;
    		while(K>sum[i]) i++;  //i表示加到第i位数时,序列长度到了K
    		K-=sum[i-1]; 
    		//假设在第i位数中,加到第certain项,长度大于等于K
    		ll certain=LowerBound(9*qpow(10,i-1),i);
    		ll certainValue=qpow(10,i-1)+certain-1;
    		K=K-sumOfSeries(base[i-1]+i,certain-1,i);
    		
    		//从1-certainValue中找下界
    		ll dst=0,num=0;
    		ll L=1,R=certainValue; 
    		while(L<=R)
    		{
    			ll Mid=(L+R)/2;
    			int len=(int)log10((double)Mid)+1;
    			ll now=base[len-1]+len*(Mid-qpow(10,len-1)+1);
    			if(now>=K) dst=Mid,num=now-K+1,R=Mid-1;
    			else L=Mid+1; 
    		}
    		//答案就是dst从右往左数的第num位
    		int len=(int)log10((double)dst)+1;
    		ll ans=dst/( qpow(10,num-1) ) %10; 
    		cout<<ans<<endl;
    	} 
    }

     

  • 相关阅读:
    如何在winform的numericUpDown中显示小数点
    Jquery attr 和removeAttr 的简单使用
    Linux下的多进程编程初步(转载)
    扩展GCD和线性模方程组
    05、Flutter常用组件
    12、Flutter组件装饰
    10、Flutter资源和图片
    09、Flutter手势控制
    04、FlutterDart语法
    07、FluterCupertino
  • 原文地址:https://www.cnblogs.com/qingoba/p/12719901.html
Copyright © 2020-2023  润新知