目录
A题:煤球数量
有一堆煤球,堆成三角棱锥形。具体: 第一层放1个,
第二层3个(排列成三角形), 第三层6个(排列成三角形), 第四层10个(排列成三角形), …
如果一共有100层,共有多少个煤球?
请填表示煤球总数目的数字。
【Solution】
暴力枚举。
【Code】
#include<iostream>
using namespace std;
const int N = 100;
int main(){
int res = 0;
int t = 0;
for(int i = 1; i <= N; ++i){
t += i;
res += t;
}
cout << res << endl; // 171700
return 0;
}
B题:凑算式
这个算式中 A ~ I 代表 1 ~ 9 的数字,不同的字母代表不同的数字。
比如:
6 + 8 / 3 + 952 / 714 就是一种解法,
5 + 3 / 1 + 972 / 486 是另一种解法。
这个算式一共有多少种解法?
【Solution】
暴力枚举。
10 的 9 的次方,一到两秒可以计算完。
【Notice】
判断不能相同的时候,不能写在for语句退出的判断中,这样会导致后面数据遍历不到。
【Code】
#include<iostream>
using namespace std;
const int N = 100;
bool judge(int a, int b, int c, int d, int e, int f, int g, int h, int i){
int num1 = (g * 100 + h * 10 + i) * c * a;
int num2 = (b * (g * 100 + h * 10 + i));
int num3 = (d * 100 + e * 10 + f) * c;
int num4 = (g * 100 + h * 10 + i) * c;
return num1 + num2 + num3 == num4 * 10;
}
int main(){
int ans = 0;
// 注意:不等于的判断不能写在for的判断里面,要写在外面。
for(int a = 1; a <= 9; a ++)
for(int b = 1; b <= 9; b ++)
for(int c = 1; c <= 9; c ++)
for(int d = 1; d <= 9; d ++)
for(int e = 1; e <= 9; e ++)
for(int f = 1; f <= 9; f ++)
for(int g = 1; g <= 9; g ++)
for(int h = 1; h <= 9; h ++)
for(int i = 1; i <= 9; i ++)
if(a != b && a != c && a != d && a != e && a != f && a != g && a != h && a != i
&& b != c && b != d && b != e && b != f && b != g && b != h && b != i
&& c != d && c != e && c != f && c != g && c != h && c != i
&& d != e && d != f && d != g && d != h && d != i
&& e != f && e != g && e != h && e != i
&& f != g && f != h && f != i
&& g != h && g != i
&& h != i
&& judge(a, b, c, d, e, f, g, h, i)) ans ++;
cout << ans << endl; // 29
return 0;
}
C题:生日蜡烛
某君从某年开始每年都举办一次生日party,并且每次都要吹熄与年龄相同根数的蜡烛。
现在算起来,他一共吹熄了236根蜡烛。
请问,他从多少岁开始过生日party的?
请填写他开始过生日party的年龄数。
【Solution】
暴力枚举
【CODE】
#include<iostream>
using namespace std;
int main(){
int res = 0;
for(int i = 1; i < 100; ++i){
res = 0;
for(int j = i; j < 100; ++j){
res += j;
if(res == 236) cout << i << endl; // 26
}
}
return 0;
}
D题:快速排序
排序在各种场合经常被用到。
快速排序是十分常用的高效率的算法。
其思想是:先选一个“标尺”,用它把整个队列过一遍筛子,
以保证:其左边的元素都不大于它,其右边的元素都不小于它。
这样,排序问题就被分割为两个子区间。再分别对子区间排序就可以了。
下面的代码是一种实现,请分析并填写划线部分缺少的代码。#include <stdio.h> void swap(int a[], int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; } int partition(int a[], int p, int r) { int i = p; int j = r + 1; int x = a[p]; while(1){ while(i<r && a[++i]<x); while(a[--j]>x); if(i>=j) break; swap(a,i,j); } //______________________; return j; } void quicksort(int a[], int p, int r) { if(p<r){ int q = partition(a,p,r); quicksort(a,p,q-1); quicksort(a,q+1,r); } } int main() { int i; int a[] = {5,13,6,24,2,8,19,27,6,12,1,17}; int N = 12; quicksort(a, 0, N-1); for(i=0; i<N; i++) printf("%d ", a[i]); printf("\n"); return 0; }
【Code】
a[j] = x;
or swap(a, p, j);
E题:抽签(递归)
X星球要派出一个5人组成的观察团前往W星。
其中:
A国最多可以派出4人。
B国最多可以派出2人。
C国最多可以派出2人。
…
那么最终派往W星的观察团会有多少种国别的不同组合呢?
下面的程序解决了这个问题。数组a[] 中既是每个国家可以派出的最多的名额。
程序执行结果为:
DEFFF
CEFFF
CDFFF
CDEFF
CCFFF
CCEFF
CCDFF
CCDEF
BEFFF
BDFFF
BDEFF
BCFFF
BCEFF
BCDFF
BCDEF
…(以下省略,总共101行)
#include <stdio.h> #define N 6 #define M 5 #define BUF 1024 // 人数数组,k表示国别的种类,m表示剩余的名额,b表示答案数组 void f(int a[], int k, int m, char b[]) { int i,j; if(k==N){ b[M] = 0; if(m==0) printf("%s\n",b); return; } for(i=0; i<=a[k]; i++){ for(j=0; j<i; j++) b[M-m+j] = k+'A'; //______________________; //填空位置 } } int main() { int a[N] = {4,2,2,1,1,3}; char b[BUF]; f(a,0,M,b); return 0; }
【Code】
f(a, k + 1, m - i, b);
F题:方格填数(全排列)
如下的10个格子
填入 0 ~ 9 的数字。要求:连续的两个数字不能相邻(左右、上下、对角都算相邻)。
一共有多少种可能的填数方案?
【解题思路】
0~9全排列(递归),按条件判断。
【Code】
#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
int res;
vector<int> a;
bool st[12];
void dfs(int s){
if(s == 10){
if(!(
abs(a[0] - a[1]) == 1 ||
abs(a[0] - a[4]) == 1 ||
abs(a[0] - a[5]) == 1 ||
abs(a[0] - a[3]) == 1 ||
abs(a[1] - a[2]) == 1 ||
abs(a[1] - a[6]) == 1 ||
abs(a[1] - a[4]) == 1 ||
abs(a[1] - a[5]) == 1 ||
abs(a[2] - a[6]) == 1 ||
abs(a[2] - a[5]) == 1 ||
abs(a[3] - a[4]) == 1 ||
abs(a[3] - a[8]) == 1 ||
abs(a[3] - a[7]) == 1 ||
abs(a[4] - a[5]) == 1 ||
abs(a[4] - a[9]) == 1 ||
abs(a[4] - a[8]) == 1 ||
abs(a[4] - a[7]) == 1 ||
abs(a[5] - a[6]) == 1 ||
abs(a[5] - a[9]) == 1 ||
abs(a[5] - a[8]) == 1 ||
abs(a[6] - a[9]) == 1 ||
abs(a[7] - a[8]) == 1 ||
abs(a[8] - a[9]) == 1 )) res ++;
return;
}
for(int i = 0; i < 10; ++i)
if(!st[i]){
st[i] = true;
a.push_back(i);
dfs(s + 1);
a.pop_back();
st[i] = false;
}
}
int main(){
dfs(0);
cout << res << endl;
return 0;
}
// 使用next_permutation函数算全排列
#include<algorithm>
#include<iostream>
using namespace std;
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int main(){
int res = 0;
while(next_permutation(a, a + 10)){
if(!(
abs(a[0] - a[1]) == 1 ||
abs(a[0] - a[4]) == 1 ||
abs(a[0] - a[5]) == 1 ||
abs(a[0] - a[3]) == 1 ||
abs(a[1] - a[2]) == 1 ||
abs(a[1] - a[6]) == 1 ||
abs(a[1] - a[4]) == 1 ||
abs(a[1] - a[5]) == 1 ||
abs(a[2] - a[6]) == 1 ||
abs(a[2] - a[5]) == 1 ||
abs(a[3] - a[4]) == 1 ||
abs(a[3] - a[8]) == 1 ||
abs(a[3] - a[7]) == 1 ||
abs(a[4] - a[5]) == 1 ||
abs(a[4] - a[9]) == 1 ||
abs(a[4] - a[8]) == 1 ||
abs(a[4] - a[7]) == 1 ||
abs(a[5] - a[6]) == 1 ||
abs(a[5] - a[9]) == 1 ||
abs(a[5] - a[8]) == 1 ||
abs(a[6] - a[9]) == 1 ||
abs(a[7] - a[8]) == 1 ||
abs(a[8] - a[9]) == 1 )) res ++;
}
cout << res << endl; // 1580
return 0;
}
G题:剪邮票(dfs)
问题描述
如图,有 12 张连在一起的 12 生肖的邮票。
现在你要从中剪下 5 张来,要求必须是连着的(仅仅连接一个角不算相连)比如下图中,粉红色所示部分就是合格的剪取。
请你计算,一共有多少种不同的剪取方法。
【Solution】
枚举所有5个元素的情况,通过dfs判断是否相连。
【Notice】
巧妙的边界处理:去除 5 和 10 这两个数当边界
【CODE】
#include<iostream>
#include<cstring>
using namespace std;
// 去除了5和10,有利于判断右端的边界
int num[] = {1, 2, 3, 4, 6, 7, 8, 9 ,11, 12, 13, 14}; // 避免边界问题
int dir[] = {-1, 1, -5, 5}; // 上下左右的遍历
int s[5]; // 保存数据
bool st[5]; // 标记数据
void dfs(int n){
// cout << "dfs" << endl;
for(int i = 0; i < 4; ++i){
int t = s[n] + dir[i];
if(t < 1 || t > 14 || t == 5 || t == 10) continue;
for(int j = 0; j < 5; ++j)
if(t == s[j] && !st[j]){ // 判断是否为杯选取的数
st[j] = true;
dfs(j);
}
}
}
int main(){
int ans = 0;
// 遍历数组的组合情况
for(int a = 0; a < 12; ++a)
for(int b = a + 1; b < 12; ++b)
for(int c = b + 1; c < 12; ++c)
for(int d = c + 1; d < 12; ++d)
for(int e = d + 1; e < 12; ++e){
s[0] = num[a];
s[1] = num[b];
s[2] = num[c];
s[3] = num[d];
s[4] = num[e];
// cout << "123" << endl;
memset(st, false, sizeof st);
st[0] = true;
dfs(0);
bool flag = true;
for(int i = 0; i < 5; ++i){ // 检查是否五个数相邻
if(!st[i]) flag = false;
}
if(flag) ans ++;
}
// cout << "123" << endl;
cout << ans <<endl; // 116
return 0;
}
H题:四平方和(化繁为简)
四平方和定理,又称为拉格朗日定理:
每个正整数都可以表示为至多44 个正整数的平方和。
如果把 0 包括进去,就正好可以表示为 4 个数的平方和。
比如:
5=02+02+12+22
7=12+12+12+22对于一个给定的正整数,可能存在多种平方和的表示法。
要求你对 4 个数排序:
0≤a≤b≤c≤d
并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法。
输入格式
输入一个正整数 N。
输出格式
输出4个非负整数,按从小到大排序,中间用空格分开。
数据范围
0<N<5*106
输入样例:
5
输出样例:
0 0 1 2
【Solution】
四重循环优化三重循环。
【Code】
// 三重暴力O(n * n * n)
#include<iostream>
#include<cmath>
using namespace std;
const int N = 5 * 1e6 + 10;
int n;
int main(){
cin >> n;
int sqr_n = sqrt(n);
for(int a = 0; a <= sqr_n; ++a)
for(int b = a; b <= sqr_n; ++b)
for(int c = b; c <= sqr_n; ++c){
int d = sqrt(n - a * a - b * b - c * c);
if(n == a * a + b * b + c * c + d * d){
if(c > d) swap(c, d);
cout << a << " " << b << " " << c << " " << d;
return 0;
}
}
}
【Solution2】
先将后两个数的平方和存入哈希表中;再遍历前两个数据,并判断四者和是否符合即可。
【Code2】
// O(n * n)
#include<iostream>
#include<cmath>
using namespace std;
const int N = 5 * 1e6 + 10;
int h[N];
int main(){
int n;
cin >> n;
for(int i = 0; i * i <= n / 2; ++i) // 除二是小于d做准备
for(int j = i; j * j + i * i <= n; ++j)
if(!h[i * i + j * j]) h[i * i + j * j] = i + 1; // 加1 是为了下面判断做的
for(int i = 0; i * i * 4 <= n; ++i)
for(int j = i; i * i + j * j <= n / 2; ++j)
if(h[n - i * i - j * j]){ // 这里判断会出现 0,上面加1用再这里
int c = h[n - i * i - j * j] - 1;
int d = int(sqrt(n - i * i - c * c - j * j) * 1.0);
cout << i <<' ' << j <<' ' << c << " " << d;
return 0;
}
}
I题:交换瓶子(找规律)
有 N 个瓶子,编号 1∼N,放在架子上。
比如有 5 个瓶子:
2 1 3 5 4
要求每次拿起 2 个瓶子,交换它们的位置。
经过若干次后,使得瓶子的序号为:
1 2 3 4 5
对于这么简单的情况,显然,至少需要交换 2 次就可以复位。
如果瓶子更多呢?你可以通过编程来解决。
输入格式
第一行包含一个整数 N,表示瓶子数量。
第二行包含 N 个整数,表示瓶子目前的排列状况。
输出格式
输出一个正整数,表示至少交换多少次,才能完成排序。
数据范围
1≤N≤10000,
输入样例1:
5 3 1 2 5 4
输出样例1:
3
输入样例2:
5 5 4 3 2 1
输出样例2:
2
【Solution】
第一重找到对应不相等的位置,接着再找和其位置相等的数据交换即可。
【CODE】O(n * n)
// 暴力遍历
#include<iostream>
#include<cstring>
using namespace std;
const int N = 10010;
int n;
int a[N];
int main(){
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
int ans = 0;
for(int i = 1; i <= n; ++i)
if(a[i] != i) // 找对应的位置,并替换.
for(int j = i + 1; j <= n; ++j)
if(a[j] == i){
swap(a[i], a[j]);
ans ++;
break;
}
cout << ans << endl;
return 0;
}
【Solution2】
我们将所有位置重叠的数据记为一个集体。
【Code2】
// 优化算法, 时间复杂度O(n)
// 图解https://www.acwing.com/solution/content/7917/;晚上写
#include<iostream>
using namespace std;
const int N = 10010;
int n;
int a[N];
bool st[N];
int main(){
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
int cnt = 0;
for(int i = 1; i <= n; ++i)
if(!st[i]){
cnt ++;
for(int j = i; !st[j] ; j = a[j]) st[j] = true;
}
cout << n - cnt << endl;
}
J题:最大比例(数学知识)(还不是太理解)
X星球的某个大奖赛设了 M 级奖励。
每个级别的奖金是一个正整数。
并且,相邻的两个级别间的比例是个固定值。
也就是说:所有级别的奖金数构成了一个等比数列。
比如:16,24,36,54,其等比值为:3/2。
现在,我们随机调查了一些获奖者的奖金数。
请你据此推算可能的最大的等比值。
输入格式
第一行为数字 N ,表示接下的一行包含 N 个正整数。
第二行 N 个正整数 Xi,用空格分开,每个整数表示调查到的某人的奖金数额。
输出格式
一个形如 A/B 的分数,要求 A、B 互质,表示可能的最大比例系数。
数据范围
0<N<100
0<Xi<1012
数据保证一定有解。输入样例1:
3 1250 200 32
输出样例1:
25/4
输入样例2:
4 3125 32 32 200
输出样例2:
5/2
输入样例3:
3 549755813888 524288 2
输出样例3:
4/1
【Solution】
先将数据排序,并计算于以a[0]为分母,a[i]为分子的最简式;分别保存在 b[] 和 c[] 中。因为要保存分子分母,使其达到最简,所以使用辗转相除法直接计算除最大公约数。
接下来对每个分子,分母互相计算其最大公约数(化简的),就使用更相减损法。
【Code】
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 110;
int n;
// a存储数据,b存储分子,c存储分母
LL a[N], b[N], c[N];
LL gcd(LL a, LL b){
return b ? gcd(b, a % b) : a;
}
LL gcd_sub(LL a, LL b){
if(a < b) swap(a, b);
return b == 1 ? a : gcd_sub(b, a / b);
}
int main(){
cin >> n;
for(int i = 0; i < n; ++i){
cin >> a[i];
}
sort(a, a + n);
int cnt = 0;
for(int i = 1; i < n; ++i){
if(a[i] != a[i - 1]){
// 这里是用辗转相除法计算最大公约数
// 便于 后面的要除到达最简。
LL t = gcd(a[i], a[0]);
b[cnt] = a[i] / t;
c[cnt] = a[0] / t;
// 所有分数都是公比的k次方
cnt ++;
}
}
LL up = b[0], down = c[0];
for(int i = 1; i < cnt; ++i){
// 这里使用更相减损法,获得最大的公约数并且化简得
up = gcd_sub(up, b[i]);
down = gcd_sub(down, c[i]);
}
cout << up << "/" << down << endl;
return 0;
}