题目列表:
2013年:黄金连分数,翻硬币
2014年:切面条
2015年:移动距离
2016年:煤球数目
2018年:螺旋折线
2020年:蛇形填数,平面切分,子串分值和
2022年模拟赛:螺旋矩阵
1.黄金连分数
黄金分割数0.61803… 是个无理数,这个常数十分重要,在许多工程问题中会出现。有时需要把这个数字求得很精确。
对于某些精密工程,常数的精度很重要。也许你听说过哈勃太空望远镜,它首次升空后就发现了一处人工加工错误,对那样一个庞然大物,其实只是镜面加工时有比头发丝还细许多倍的一处错误而已,却使它成了“近视眼”!!
言归正传,我们如何求得黄金分割数的尽可能精确的值呢?有许多方法。
比较简单的一种是用连分数:1
黄金数 = ---------------------
1
1 + -----------------
1
1 + -------------
1
1 + ---------
1 + ...
这个连分数计算的“层数”越多,它的值越接近黄金分割数。
请你利用这一特性,求出黄金分割数的足够精确值,要求四舍五入到小数点后100位。
小数点后3位的值为:0.618
小数点后4位的值为:0.6180
小数点后5位的值为:0.61803
小数点后7位的值为:0.6180340
(注意尾部的0,不能忽略)
你的任务是:写出精确到小数点后100位精度的黄金分割值。
注意:尾数的四舍五入! 尾数是0也要保留!
显然答案是一个小数,其小数点后有100位数字,请通过浏览器直接提交该数字。
答案:
0.6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911375
代码:
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal a = new BigDecimal(1);
BigDecimal b = new BigDecimal(2);
for(int i = 0;i < 500;i++){
BigDecimal temp = a;
a = b;
b = b.add(temp);
}
System.out.println(a.divide(b,100,BigDecimal.ROUND_HALF_DOWN));
}
}
2.翻硬币
问题描述
小明正在玩一个“翻硬币”的游戏。桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。
比如,可能情形是:**oo***oooo
如果同时翻转左边的两个硬币,则变为:oooo***oooo
现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?
我们约定:把翻动相邻的两个硬币叫做一步操作,那么要求:
输入格式
两行等长的字符串,分别表示初始状态和要达到的目标状态。每行的长度<1000输出格式
一个整数,表示最小操作步数。样例输入1
**********
o****o****
样例输出1
5
样例输入2
*o**o***o***
*o***o**o***
样例输出2
1
分析:
遍历字符串,依次寻找与目标字符串第一个不同的位置和第二个不同的位置,如果找到一对,就继续往后寻找
ans = sum(第二个不同的位置-第一个不同的位置)
代码:
#include<iostream>
using namespace std;
int main(){
string src,s;
cin >> src;
cin >> s;
int first,second;
bool flag = true;
int ans = 0;
for(int i = 0;i < s.length();i++){
if(s[i] != src[i]){
if(flag){
first = i;
flag = false;
}else{
second = i;
ans += second-first;
flag = true;
}
}
}
cout << ans;
return 0;
}
3.切面条
一根高筋拉面,中间切一刀,可以得到2根面条。
如果先对折1次,中间切一刀,可以得到3根面条。
如果连续对折2次,中间切一刀,可以得到5根面条。
那么,连续对折10次,中间切一刀,会得到多少面条呢?
答案:1025
分析:
0 2
1 3
2 5
规律:
f(n) = 2*f(n-1) - 1
代码:
#include<iostream>
using namespace std;
int main(){
int a[11] = {0};
a[0] = 2;
a[1] = 3;
for(int i = 2;i <= 10;i++){
a[i] = 2*a[i-1] - 1;
}
cout << a[10];
return 0;
}
4.移动距离
题目描述
X星球居民小区的楼房全是一样的,并且按矩阵样式排列。
其楼房的编号为 1,2,3…
当排满一行时,从下一行相邻的楼往反方向排号。
比如:当小区排号宽度为 6 时,开始情形如下:
1 2 3 4 5 6
12 11 10 9 8 7
13 14 15 …我们的问题是:已知了两个楼号 m和 n,需要求出它们之间的最短移动距离(不能斜线方向移动)。
输入格式
输入共一行,包含三个整数 w,m,n,w 为排号宽度,m,n为待计算的楼号。
输出格式
输出一个整数,表示 m,n两楼间最短移动距离。
数据范围
1≤w,m,n≤10000
输入样例:
6 8 2
输出样例:
4
分析:
m和n所对应的行号就是(n-1)/w,(m-1)/w;所对应的列号需要分奇偶性,如果行号为偶数,那么列号就是(n-1)%w,如果行号是奇数,那么列号就是w-1-(n-1)%w
两栋楼房之间的最短距离就是横坐标的差值+纵坐标的差值
代码:
#include<iostream>
#include<algorithm>
using namespace std;
int w,m,n;
struct P{
int x,y;
};
P getSex(int n){
P p;
p.x = (n-1)/w;
if(p.x % 2 == 0){
p.y = (n-1)%w;
}else{
p.y = w-1-(n-1)%w;
}
return p;
}
int main(){
cin >> w >> m >> n;
P p1 = getSex(m);
P p2 = getSex(n);
cout << abs(p1.x-p2.x) + abs(p1.y-p2.y);
return 0;
}
5.煤球数目
有一堆煤球,堆成三角棱锥形。
具体: 第一层放1个,
第二层3个(排列成三角形),
第三层6个(排列成三角形),
第四层10个(排列成三角形), …
如果一共有100层,共有多少个煤球?
请填表示煤球总数目的数字。
答案:171700
分析:
1 1
2 3
3 6
4 10
我们会发现1+2=3;3+3=6;6+4=10
代码:
#include<iostream>
using namespace std;
int main(){
int sum = 0,n = 0;
for(int i = 1;i <= 100;i++){
n += i;
sum += n;
}
cout << sum;
return 0;
}
6.螺旋折线
如图p1.png所示的螺旋折线经过平面上所有整点恰好一次。
对于整点(X, Y),我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。
例如dis(0, 1)=3, dis(-2, -1)=9
给出整点坐标(X, Y),你能计算出dis(X, Y)吗?
【输入格式】
X和Y
对于40%的数据,-1000 <= X, Y <= 1000
对于70%的数据,-100000 <= X, Y <= 100000
对于100%的数据, -1000000000 <= X, Y <= 1000000000
【输出格式】
输出dis(X, Y)
【样例输入】
0 1
【样例输出】
3
分析:
将所有的边拉直,让这个图形成为一个迭代的小正方形
将(1,1),(2,2),(3,3)这样的点作为中间的媒介,正方形四条边上的点都可以根据它求出
例如:
(1,1) : 4
上边和左边(0,1):3 (-1,1):2 (-1,0):1
可以发现:4 - [(1-0)+(1-1)] = 3;4 - [(1-(-1))+(1-1)] = 2……
右边和下边 (1,0):5 (1,-1):6 (0,-1):7
可以发现:4 + [(1-1)+(1-0)] = 5;4 + [(1-1)+(1-(-1))] = 6……ta
代码:
#include<iostream>
#include<cmath>
using namespace std;
int main(){
long long x,y;
cin >> x >> y;
long long n = max(abs(x),abs(y));
long long base = 4*n*n;
long long bx = n,by = n;
if(x == bx || y == -by){//正方形右边和下边
cout << base + (abs(bx-x) + abs(by-y));
}else{//正方形上边和左边
cout << base - (abs(bx-x) + abs(by-y));
}
return 0;
}
7.蛇形填数
如下图所示,小明用从 1 开始的正整数 “蛇形” 填充无限大的矩阵。
容易看出矩阵第二行第二列中的数是 5,请你计算矩阵中第 20 行第 20 列的数是多少?
答案:761
分析:
我们只观察对角线上的元素:
(1,1):1 (2,2):5 (3,3):13……
(1,1)在斜行的第0行;(2,2)在斜行的第2行;(3,3)在斜行的第4行……(n,n)在斜行的第2*(n-1)行
所以,(1,1):1 (2,2):1+2+2 (3,3):1+2+3+4+3
(20,20) : = 761
8.平面切分
【问题描述】
平面上有 N 条直线,其中第 i 条直线是 y = Ai · x + Bi。
请计算这些直线将平面分成了几个部分。
【输入格式】
第一行包含一个整数 N。
以下 N 行,每行包含两个整数 Ai; Bi。
【输出格式】
一个整数代表答案。
【样例输入】
3
1 1
2 2
3 3
【样例输出】
6
【评测用例规模与约定】
对于 50% 的评测用例, 1 ≤ N ≤ 4, −10 ≤ Ai; Bi ≤ 10。
对于所有评测用例, 1 ≤ N ≤ 1000, −100000 ≤ Ai; Bi ≤ 100000。
分析:
规律:第一条直线会产生2个平面,增加一条之前没加入过的直线,平面数+1,如果这条新加的直线与之前加入的直线产生k个不相同的交点,则平面数再+k
注意:
他有可能会给重复数据,这样就会产生重边,加入一条重边,我们就给他打上标记,当他不存在就好了
另外,注意数据类型的设定,平面数可能会超出int的范围,所以面对的数据量较大时建议使用long long类型;当进行数据运算时,如果参与运算的数的数据类型不一致要进行强制转换
代码:
#include<iostream>
#include<set>
using namespace std;
const int MAX_N = 1010;
int L[MAX_N][2];//存储A,B
int main(){
int N;
cin >> N;
long long ans = 1;
for(int i = 0;i < N;i++){
cin >> L[i][0] >> L[i][1];
bool flag = false;
for(int j = 0;j < i;j++){
if((L[j][0] == L[i][0]) && (L[j][1] == L[i][1])){
flag = true;
break;
}
}
if(!flag){//若不是重边,再判断交点数
set<pair<long double,long double> > points;//记录每一条边加进来后与已有直线相交的不同位置的点
for(int k = 0;k < i;k++){
if(L[k][0] != L[i][0]){
long double x = (long double)(L[i][1]-L[k][1])/(L[k][0]-L[i][0]);
long double y = (long double)(L[i][0]*L[k][1]-L[k][0]*L[i][1])/(L[i][0]-L[k][0]);
points.insert(make_pair(x,y));//set自动去重
}
}
ans += points.size()+1;
}
}
cout << ans;
return 0;
}
9.子串分值和
问题描述
对于一个字符串S ,我们定义 S 的分值 f(S) 为S 中出现的不同的字符个数。例如 f(“aba”)=2,f(“abc”)=3, f(“aaa”)=1。现在给定一个字符串 S[0…n-1](长度为n ),请你计算对于所有S 的非空子串S[i…j] ,(0<=i<=j<=n-1)的和是多少。
输入格式
输入一行包含一个由小写字母组成的字符串 。输出格式
输出一个整数表示答案。样例输入
ababc
样例输出28
样例说明
子串 f值a 1
ab 2
aba 2
abab 2
ababc 3
b 1
ba 2
bab 2
babc 3
a 1
ab 2
abc 3
b 1
bc 2
c 1
评测用例规模与约定(n为正整数)
对于 20% 的评测用例:
n<=10
对于 40%的评测用例:
n<=100
对于 60%的评测用例:
n<=1000
对于 80%的评测用例:
n<=10000
对于所有评测用例:
n<=100000
分析:
有时候一个例子我们是发现不了规律的,所以不妨多举几个
做到这种题我就会往贡献度方面想,这个字母贡献了最终答案的多少啊……
这里我就举一个例子(大家可以自己在底下多模拟几个):
下标 1 2 3 4 5
a b a b c
贡献度 5 8 6 4 5
我们发现: 5*1 4*2 3*2 2*2 1*5
5*(1-0) 4*(2-0) 3*(3-1) 2*(4-2) 1*(5-0)
规律(在写代码的时候下标从0开始):
每个位置的字母的贡献度= (字母总个数-该字母下标)*(该字母下标-该字母上一次出现的位置下标)
我们将a,b,c三个字母的初始下标都设为-1
代码:
#include<iostream>
#include<cstring>
#include<map>
using namespace std;
int main(){
string s;
cin >> s;
map<char,int> last;
for(char i = 'a';i <= 'z';i++){
last[i] = -1;
}
long long ans = 0;
int n = s.length();
for(int i = 0;i < n;i++){
ans += (long long)(n-i)*(i-last[s[i]]);//这里的运算结果可能超过int的范围,需要强制转换为long long类型
last[s[i]] = i;
}
cout << ans;
return 0;
}
10.螺旋矩阵
【问题描述】
对于一个n 行 m 列的表格,我们可以使用螺旋的方式给表格依次填上正整数,我们称填好的表格为一个螺旋矩阵。
例如,一个 4 行 5 列的螺旋矩阵如下:
1 2 3 4 5
14 15 16 17 6
13 20 19 18 7
12 11 10 9 8【输入格式】
第一行包含两个整数 n, m,分别表示螺旋矩阵的行数和列数。
第二行包含两个整数 r, c,表示要求的行号和列号。【输出格式】
输出一个整数,表示螺旋矩阵中第 r 行第 c 列的元素的值。【样例输入】
4 5
2 2
【样例输出】
15
【评测用例规模与约定】
对于 30% 的评测用例,2 ≤ n, m ≤ 20
对于 70% 的评测用例,2 ≤ n, m ≤ 100
对于所有评测用例,2 ≤ n, m ≤ 1000,1 ≤ r ≤ n,1 ≤ c ≤ m
分析:
最重要的就是定界,上下左右四个角作为转折点
代码:
#include <iostream>
using namespace std;
int a[1010][1010];
int main(){
int n, m, x, y;
cin >> n >> m;
cin >> x >> y;
int num = 1;
int l = 0, r = m - 1, u = 0, d = n - 1;
while(num <= n * m){//将所有的数都填进数组
for (int i = l; i <= r; i++){//向右
a[u][i] = num++;
}
u++;
for (int i = u; i <= d; i++){//向下
a[i][r] = num++;
}
r--;
for (int i = r; i >= l; i--){//向左
a[d][i] = num++;
}
d--;
for(int i = d;i >= u; i--){//向上
a[i][l] = num++;
}
l++;
}
cout << a[x - 1][y - 1];
return 0;
}