poj1019

本文深入探讨了一个看似简单的数学问题,实则隐藏着多重陷阱。作者通过详细分析,揭示了超过一位数的数字在求和时的位数变化规律,并提供了二分查找和特殊处理方法来解决这类问题。文章不仅给出了正确的求和方法,还附带了优化后的代码实现,帮助读者理解并解决类似问题。

这道题目不难但是很麻烦,而且题意容易误解。一开始我就没有完全搞懂题意,导致错误。最容易出错的地方在超过1位数的数字占用位数不再是1了,而是其数字个数,这也应该不算什么难点,只是容易搞成所有数字都是一位(一时就脑袋蒙了),若所有数字都是1位,那么就简单多了,直接用求和公式k*(1+k)/2,求出S1+S2+……+SK的所有数字个数,然后稍加处理即可。而这里不同数字要按照实际位数计算。不过分析思路还是同上。只是处理起来要麻烦多了。

分析如下:

可以二分查找出小于或等于n(即题目输入的i值)的k,即 S1+S2+……+SK<=n,具体计算方法稍后说明。这里有两种情况。

1)若为相等,那么第n位即为数字K的最后一位,直接输出

2)若小于,那么第n位必定为数字K+1序列数字中的某一位。

情形2)更复杂,也可以分两种情况:

i)当第n位数字为刚好1位数,2位数,……,k位数的结束位时,则必定为9

ii)否则,第n位数字为这些位中某个数的中间一位数字

而第ii)种情况又可以细分为如下两种情况:

iii 1)第n为数字为数字的末尾位,特殊处理

iii 2)否则,一般处理

这里详细介绍一下上面所述的关于如何计算S1+S2+……+Sk的问题。

这里设置两个数字first,和 num 。分别记录K位数的第一个数字和K位数的个数。例如1位数的第一个数字为1,个数为9,2位数第一个数字为10,个数为90,等等。

那么SK的计算方法为求出所有位数小于K数字位数的数字个数与它们各自位数的乘积之和(即位数小于K位数的所数字位数之,利用上面所述的两个数组),

然后加上(K-first[len(K)]+1)* len(K)。依次求出S1,S2,……SK的值,然后相加。

上述过程可以抽象为一个函数。

下面是代码:132K+94MS

#include <stdio.h>
#include <stdlib.h> //初始化first与num数组
int first[11]={0,1,10,100,1000,10000,100000,1000000,10000000,100000000};
int   num[11]={0,9,90,900,9000,90000,900000,9000000,90000000,900000000};
int Case;
__int64 n; // 输入值i
__int64 record; 
bool  trag; // 标记量
__int64 Get_num(int number){ // S1+S2+S3+……SK求和
	__int64 pro=0;  // 保存结果
	for(int j=1;j<=number;j++){ // 循环求解SK
	int i,len=0,temp=j;
	while(temp>0){ // 求解数字位数
		len++;
		temp/=10;
	}
	for(i=1;i<len;i++) // 累加比该位数小的所有数字位数之和
		pro+=num[i]*i;
	pro+=(j-first[len]+1)*len; // 加上该位数的数字位数之和
	}
	return pro; //返回求和结果
}
int Binary_search(int left,int right){ // 二分查找Sk<=n
	while(left<=right){
		int mid=(left+right)>>1;
		if(Get_num(mid)<n)
			left=mid+1;
		else if(Get_num(mid)>n)
			right=mid-1;
		else{
			trag=1; //若相等
			return mid;
		}
	}
	trag=0; // 小于
	return right; // 注意此处为right而不是left,这样才能保证Sk<n
}
int main(){
	scanf("%d",&Case); //case数
	while(Case--){
		scanf("%I64d",&n);
		int Count=Binary_search(1,35000); //经过测试35000是比较合适的数字,保证二分查找的正确性
		if(trag){ // 若相等则直接输出最后一位数字
			printf("%d\n",Count%10);
			continue;
		} //否则判断是在末尾位还是中间位
		record=n-Get_num(Count);
		//printf("%I64d ====\n",record);
		int pivot=1;
	    while(true){
			if(record-num[pivot]*pivot>0){ //若还有多余的位数,则继续分解
				record-=num[pivot]*pivot;
				pivot++;
			}
			else if(record-num[pivot]*pivot<0){ //若没有多余的位数了,则设置标记为0,为中间位
				trag=0;
				break;
			}
			else{ // 若正好相等,则为结束位,设置标记为1
				trag=1;
				break;
			}
		}
		if(trag){ //若为结束位,直接输出9
			printf("9\n");
		    continue;
		}
		if(record%pivot==0){ // 判断若属于中间数字的最后一位,则特殊处理
	    int x=first[pivot]+record/pivot-1;
		printf("%d\n",x%10);
		continue;
		} //否则一般处理
		int x=first[pivot]+record/pivot;
		int index=pivot-record%pivot+1;
		pivot=1;
		while(x>0){
			if(pivot==index){
				printf("%d\n",x%10);
				break;
			}
			x/=10;
			pivot++;
		}
	}
	return 0;
}
	


 也给出误解题意的程序(仅作参考)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int Case;
__int64 n;
bool trag;
int Binary_search(int left,int right){
	while(left<=right){
		int mid=(left+right)>>1;
		if(n<(mid+1)*mid/2)
			right=mid-1;
		else if(n>(mid+1)*mid/2)
			left=mid+1;
		else{
			trag=1;
			return mid;
		}
	}
	trag=0;
	return right;
}

int main(){
	scanf("%d",&Case);
	while(Case--){
	scanf("%I64d",&n);
	int temp=Binary_search(1,2*int(sqrt(double(n)))+1);
	if(!trag)
		printf("%I64d\n",n-(1+temp)*temp/2);
	else
		printf("%d\n",temp);
	}
	return 0;
}


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值