1999 CSP-J/NOIP复赛真题题解——秒杀T1 Cantor表,T2 回文数,T3 旅行家的预算

T1 Cantor表

题干

[NOIP1999 普及组] Cantor 表

题目描述

现代数学的著名证明之一是 Georg Cantor 证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的:

我们以 Z 字形给上表的每一项编号。第一项是 1 / 1 1/1 1/1,然后是 1 / 2 1/2 1/2 2 / 1 2/1 2/1 3 / 1 3/1 3/1 2 / 2 2/2 2/2,…

输入格式

整数 N N N 1 ≤ N ≤ 1 0 7 1 \leq N \leq 10^7 1N107)。

输出格式

表中的第 N N N 项。

样例 #1

样例输入 #1

7

样例输出 #1

1/4

解析

如果我们把斜着的一列看作一组,那么我们就会发现第n组一共会有n个分数,并且每个分数的分子和分母的和都为(n+1),且从左下到右上,分子从n开始递减,分母从1开始递加,则可以先求出第N个数是在第几组的,再判断这个数是这一组的第几个数即可。但是要注意的是这是Z字形的,则奇数组数和偶数组数的结果是完全相反的

时间复杂度

O(n),但是逆天地几乎全都是常数项!!!!经简单计算可得结果!!!

空间复杂度

是逆天的O(1)!!!!又都是常数项!!!不会随着N的增大而变多!!!

实现代码

#include <bits/stdc++.h>
using namespace std ;
long long n,ans = 1 ;
//第n个数,ans表示第n个数是第几组数中的
int main(){
	scanf("%lld",&n) ;
	while(n > ans)n -= (ans ++) ;
    //计算第n个数是第几组数中的
	if(ans & 1)n = (ans + 1 - n) ;
    //因为是Z字形,所以奇数行与偶数行结果相反
	printf("%lld/%lld",n,(ans + 1 - n)) ;
    //输出结果
	return 0 ;
}

T2 回文数

题干

[NOIP1999 普及组] 回文数

题目描述

若一个数(首位不为零)从左向右读与从右向左读都一样,我们就将其称之为回文数。

例如:给定一个十进制数 56 56 56,将 56 56 56 65 65 65(即把 56 56 56 从右向左读),得到 121 121 121 是一个回文数。

又如:对于十进制数 87 87 87

STEP1: 87 + 78 = 165 87+78=165 87+78=165
STEP2: 165 + 561 = 726 165+561=726 165+561=726
STEP3: 726 + 627 = 1353 726+627=1353 726+627=1353
STEP4: 1353 + 3531 = 4884 1353+3531=4884 1353+3531=4884

在这里的一步是指进行了一次 N N N 进制的加法,上例最少用了 4 4 4 步得到回文数 4884 4884 4884

写一个程序,给定一个 N N N 2 ≤ N ≤ 10 2 \le N \le 10 2N10 N = 16 N=16 N=16)进制数 M M M 100 100 100 位之内),求最少经过几步可以得到回文数。如果在 30 30 30 步以内(包含 30 30 30 步)不可能得到回文数,则输出 Impossible!

输入格式

两行,分别是 N N N M M M

输出格式

如果能在 30 30 30 步以内得到回文数,输出格式形如 STEP=ans,其中 ans \text{ans} ans 为最少得到回文数的步数。

否则输出 Impossible!

样例 #1

样例输入 #1

10
87

样例输出 #1

STEP=4

解析

这题一看数据范围,M100位之内!!!晕…那么正常的long long就一定行不通了。所以此时,我们的高精度加法出马!!!则我们可以像小学写竖式那样,用一个数组把一个很大的数字存储再其中,每一个数组元素都只存储一位的数据,但是数组长度大,就可以做到把每一位都保存在里面。然后一位一位地对它们进行操作(如相加,相乘,进位、借位等),就解决了数据处理的问题。

但是可能有人就要问了:可是我们列竖式不是10进制的吗?但是这里是N进制,啊!!!CPU烧了(仙气飘飘.jpg)…没有关系,在一位的数字超过10时,我们可以参考16进制,用英文字母来表示(A-10,B-11,C-12,D-13…Z-36);关于进制的问题我们可以在编写的时候“扭曲”自己的想法,强行让它满N进1。这样是不会有任何问题的,不信的话你可以列一个14进制的竖式来看一看:

如: ( 987 ) 1 4 (987)_14 (987)14 + ( 789 ) 1 4 (789)_14 (789)14 = ( 1883 ) 1 0 (1883)_10 (1883)10 + ( 1493 ) 1 4 (1493)_14 (1493)14 = ( 3376 ) 1 0 (3376)_10 (3376)10

 987

+789

---------

 1332

7+9 = 16 16 = 14 + 2 进位

8 + 8 + 1 = 17 17 = 14 + 3 进位

9 + 7 + 1 = 17 17 = 14 + 3 进位

1 = 1 (进位来的)

( 1332 ) 1 4 (1332)_14 (1332)14 = ( 3376 ) 1 0 (3376)_10 (3376)10完全正确!!!!

时间复杂度

若将运算的数组有效长度的平均长度设为A,若答案为Impossible!则B为31,否则B为答案,则时间复杂度为O(AB)

空间复杂度

若将运算数组的最长长度设为A,则空间复杂度为O(A)

实现代码

#include <bits/stdc++.h>
using namespace std ;
long long m,step ;
//进制,当前步骤数
string s ;
//用于输入大数
vector<int> t ;
void add(){
	vector<int> a(t.size() + 1,0) ;
	for(int i = 0;i < t.size();++ i){
		a[i + 1] += ((a[i] += (t[i] + t[t.size() - i - 1])) / m) ;
        //进位操作
		a[i] %= m ;
        //进位完的维护操作
	}
	while(a.size() && a[a.size() - 1] == 0)a.pop_back() ;
    //消除前缀零
	t = a ;
    //保存结果
	return ;
}
bool pd(){
    //判断是否是回文数
	for(int i = (t.size() >> 1);i >= 0;-- i)
		if(t[i] != t[t.size() - i - 1])return true ;
	return false ;
}
int main(){
	scanf("%lld",&m) ;
    //输入进制数
	cin >> s ;
    //输入大数
	for(int i = s.size() - 1;i >= 0;-- i){
		if(s[i] >= '0' && s[i] <= '9')t.push_back(s[i] ^ '0') ;
		else t.push_back(s[i] - 'A' + 10) ;
        //逆序存储大数
	}
	while(step < 31 && pd())add(),++ step ;
    //多次相加
	if(step == 31)printf("Impossible!") ;
	else printf("STEP=%d",step) ;
    //输出结果
	return 0 ;
}

T3 旅行家的预算

题干

[NOIP1999 提高组] 旅行家的预算

题目描述

一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出发时油箱是空的)。给定两个城市之间的距离 D 1 D_1 D1、汽车油箱的容量 C C C(以升为单位)、每升汽油能行驶的距离 D 2 D_2 D2、出发点每升汽油价格 P P P和沿途油站数 N N N N N N 可以为零),油站 i i i 离出发点的距离 D i D_i Di、每升汽油价格 P i P_i Pi i = 1 , 2 , … , N i=1,2,…,N i=1,2,,N)。计算结果四舍五入至小数点后两位。如果无法到达目的地,则输出 No Solution

输入格式

第一行, D 1 D_1 D1 C C C D 2 D_2 D2 P P P N N N

接下来有 N N N 行。

i + 1 i+1 i+1 行,两个数字,油站 i i i 离出发点的距离 D i D_i Di 和每升汽油价格 P i P_i Pi

输出格式

所需最小费用,计算结果四舍五入至小数点后两位。如果无法到达目的地,则输出 No Solution

样例 #1

样例输入 #1

275.6 11.9 27.4 2.8 2
102.0 2.9
220.0 2.2

样例输出 #1

26.95

提示

N ≤ 6 N \le 6 N6,其余数字 $ \le 500$。

NOIP1999 普及组第三题、提高组第三题

解析

这道题,数据范围很小,所以直接贪心+模拟

首先,NoSolution的情况就是相邻两个可加油站点距离大于满箱油所能行驶的距离,则不能到达目的地。

每次都要判断一下,当前站点所能到达的的其他站点内有没有更省钱的,如果有,就行驶到这个站点;如果没有,则加满油箱,尽量向前走,让油箱里有多余的油(因为在这一段路上加油相对而言都是不值得的)

注意:要用double(或float),否则会有精度问题!!!(输入的是浮点数!!!)

时间复杂度

O(n)

空间复杂度

O(n)

实现代码

#include <stdio.h>
double t[4] ;
typedef long long ll ;
double s,c,d,p,pi[10],di[10],ans,more ;
int n ;
int main(){
	scanf("%lf%lf%lf%lf%d",&s,&c,&d,&p,&n) ;
	pi[1] = p ;//第“1”个站点,起点
	++ n ;
	for(int i = 2;i <= n;++ i)
		scanf("%lf%lf",&di[i],&pi[i]) ;//输入中间的加油站
	di[++ n] = s ;//最后一个站点,终点
	for(int i = 2;i <= n;++ i)
		if((di[i] - di[i - 1]) > (d * c)){
			printf("No Solution") ;//判断是否会存在某次即使加
                                    //满油后也不能到达下一个站点的情况
			return 0 ;
		}
	int o = 0 ;
    for(int i = 1;i < n;i = o){
    	o = i + 1 ;
    	while(o <= n)if((di[o] - di[i]) > (d * c)) { -- o ; break ; }
    		else if(pi[o] < pi[i])break ;
    		else ++ o ;//寻找比当前站点便宜的站点
		if(pi[o] < pi[i]){//如果找到了
			ans += (di[o] - di[i] - more * d) * pi[i] / d ;
			more = 0 ;//行驶到这个站点
		}else{//否则,该站点已经是最近几个一次加油可达的站点中最优的了
			ans += (c - more) * pi[i] ;//加满油
			more = (c - ((di[o] - di[i]) / d)) ;//使邮箱内有剩余
		}
	}
	printf("%.2lf",ans) ;//保留两位小数输出答案
	return 0 ;
}

  • 23
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值