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 1≤N≤107)。
输出格式
表中的第 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
2≤N≤10 或
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 N≤6,其余数字 $ \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 ;
}