Spoj 1433 数位问题

48 篇文章 0 订阅
34 篇文章 0 订阅

题目大意:
 将 1~N(1<=N<= 15 10 )写在纸上,然后在相邻的数字间交替插入”+”和”-“,求最后的
结果。例如当 N 为 12 时,答案为:+1-2+3-4+5-6+7-8+9-1+0-1+1-1+2=5
解法分析:
这是一道稍微复杂一点的数位计数问题。
根据上述原则,我们首先探查数位确定,所有数字自由的情况。
若数位数为偶数,以 6 位为例(不妨设第一个符号为+):
+0  -0  +0  -0  +0  -0
+0  -0  +0  -0  +0  -1
+0  -0  +0  -0  +0  -2
………………………………………………………
+9  -9  +9  -9  +9  -9
此时,每一个数位的符号都是确定的,因此只需要分别计算每一位的所有数字和即可,
因为所有位 0~9 出现机会均等,因此和必然为 0。
若数位数为奇数,此情况更加简单,以 5 位为例(不妨设第一个符号为+) :
+0  -0  +0  -0  +0
-0  +0  -0  +0  -1
+0  -0  +0  -0  +2
-0  +0  -0  +0  -3 
………………………………………………………
+9  -9  +9  -9  +8
-9  +9  -9  +9  -9
可以注意到,相邻两行的和必然为-1,因此整个和很容易求出。
 于是我们编写函数 getsum1 和检验函数 check。 (getsum1 的另一个参数 k 的由来在下页
有说明)
long long getsum1( int n, int k )
//n 为自由位个数,k 为总位数(k>=n>=1)
{
 if ( k % 2 == 0 )
 {
  if ( n % 2 == 0 ) return 0;
  else
  {
   long long d = -45;
   for (int i = 0; i < n - 1; i++ ) d *= 10;
   return d;
  }
 }
 else
 {
  long long d = -1;
  for (int i = 0; i < n; i++ ) d *= 10;
  return d / 2;
 }}
long long check( int n )
{
 long long ret = 0;
 int t = 1, a[10]; 
 for (int i = 1; i <= n; i++ )
 {
  int r = 0, p = i;
  while ( p > 0 )
  {
   a[r++] = p % 10;
   p /= 10;
  }
  for (int j = r - 1; j >= 0; j-- )
  {
   ret += a[j] * t;
   t = -t;
  }
 }
 return ret;
}
 接下来,考虑带有前缀的情况,因为前缀对符号的影响,所以需要在 getsum1 处追加总
位数的参数。此时由 getsum1 处可求出自由位的数字和,因此只需再求出前缀的数字和即可。
当总位数=自由位+前缀位数为偶数时:
+1  -2  +0  -0  +0  -0
+1  -2  +0  -0  +0  -1
+1  -2  +0  -0  +0  -2
………………………………………………………
+1  -2  +9  -9  +9  -9
前缀数字和符号不变,因此只需要乘总行数即可。
总位数为奇数时:
+1  -2  +0  -0  +0
-1  +2  -0  +0  -1
+1  -2  +0  -0  +2
-1  +2  -0  +0  -3
清华附中 高逸涵
第 8 页 共 13 页
………………………………………………………
+1  -2  +9  -9  +8
-1  +2  -9  +9  -9
前缀两两相消,和为 0。
依照以上分析编写 getsum2。
long long getsum2( long long prefix, int n )
//prefix 为前缀,n 为自由位个数(n >= 1, prefix >= 1)
{
 int d = 0, t = 1;
 long long p = prefix, presum = 0;
 while ( p > 0 )
 {
  presum += (p % 10) * t;
  p /= 10;
  d ++;
  t = -t;
 }
 presum *= -t;
 for (int i = 0; i < n; i++ ) presum *= 10;
 long long ret = getsum1( n, n + d );
 if ( (d + n) % 2 == 0 ) ret += presum;
 return ret;
}
沿用上例的思路,再有了上述两个函数之后,我们继续将整个区间划分为若干段,分别
利用上述函数求和,这里不再重复叙述,只是将函数实现展示如下。(不能沿用上例递归程
序是由于上例添加前导 0 对结果不影响,而本例则不同)
long long getsum3( long long n )
//对原问题进行求和[1,n],n>=1

 if ( n < 10 )
 {
  long long ret = 0;
  for (int i = 1; i <= n; i++ )
  if ( i % 2 == 0 ) ret -= i; else ret += i;
  return ret;
 }
 long long tn = n, p = 1;
 int d = 0;
 while ( tn > 0 )
 {
  tn /= 10;
  d ++;
 }
 for (int i = 1; i < d; i++ ) p *= 10;
 long long prefix = 0, ret = 5;
 for (int j = 1; j < d - 1; j++ )
 for (int i = 1; i <= 9; i++ )
  ret -= getsum2( i, j );
 tn = n;
 while (d > 1)
 {
  for (int i = 0; i < tn / p; i++ )
  {
   if ( prefix != 0 ) 
    ret -= getsum2( prefix, d - 1 );
   prefix ++;
  }
  tn %= p; p /= 10; 

  d--; prefix *= 10;
 }
 int a[20], t = -1;
 for (int i = 0; i <= tn; i++ )
 {
  long long p = prefix + i;
  int r = 0;
  while ( p > 0 )
  {
   a[r++] = p % 10;
   p /= 10;
  }
  for (int j = r - 1; j >= 0; j-- )
  {
   ret += a[j] * t;
   t = -t;
  }
 }
 return ret;
}

AC代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;

long long getsum1( long long n, long long k ){
	if( k % 2 == 0 ){
		if( n % 2 == 0 ){
			return 0;
		}else{
			long long ans = -45;
			for( int i = 1; i < n; i++ ){
				ans *= 10;
			}
			return ans;
		}
	}else{
		long long ans = -1;
		for( int i = 1; i <= n; i++ ){
			ans *= 10;
		}
		return ans / 2;
	}
}

long long getsum2( long long prefix, long long n ){
	long long d = 0, t = 1;
	long long p = prefix, presum = 0;
	long long ans = 0;

	while( p > 0 ){
		presum += ( p % 10 ) * t;
		p /= 10;
		d++;
		t = -t;
	}
	presum *= -t;
	
	for( int i = 1; i <= n; i++ ){
		presum *= 10;
	}

	ans += getsum1( n, n + d );
	
	if( ( n + d ) % 2 == 0 ){
		ans += presum;
	}

	return ans;
}

long long getsum3( long long n ){
	if( n < 10 ){
		long long ans = 0;
		for( int i = 1; i <= n; i++ ){
			if( i % 2 == 0 ){
				ans -= i;
			}else{
				ans += i;
			}
		}
		return ans;
	}

	long long ans = 5;
	long long d = 0, tn = n, p = 1;
	
	while( tn > 0 ){
		tn /= 10;
		d++;
	}
	
	for( int j = 1; j < d - 1; j++ ){
		for( int i = 1; i <= 9; i++ ){
			ans -= getsum2( i, j );
		}
	}

	for( int i = 1; i < d; i++ ){
		p *= 10;
	}

	tn = n;
	long long prefix = 0;
	while( d > 1 ){
		for( int i = 0; i < tn / p; i++ ){
			if( prefix != 0 ){
				ans -= getsum2( prefix, d - 1 );
			}
			prefix++;
		}
		prefix *= 10;
		tn %= p;
		p /= 10;
		d--;
	}

	int a[20], r = 0, t = -1;
	for( int i = 0; i <= tn; i++ ){
		long long p = prefix + i;
		r = 0;
		while( p > 0 ){
			a[r++] = p % 10;
			p /= 10;
		}
		for( int j = r - 1; j >= 0; j-- ){
			ans += a[j] * t;
			t = -t;
		}
	}

	return ans;
}

int main(){
	long long n;
	while( cin >> n, n ){
		cout << getsum3( n ) << endl;
	}
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值