Week8 CSP-M2 C - 咕咕东的奇妙序列 Gym - 270737E

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

 

比赛时的想法:

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;
	} 
}

 

猜你喜欢

转载自www.cnblogs.com/qingoba/p/12719901.html
今日推荐