1.煤球数目
有一堆煤球,堆成三角棱锥形。具体:第一层放1个,第二层3个(排列成三角形),第三层6个(排列成三角形),第四层10个(排列成三角形),…如果一共有100层,共有多少个煤球?
请填表示煤球总数目的数字。注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
- 思路:分别是1、3、6、10…,其实就是找规律,可以看出来每次+2、+3、+4…即An=An-1+n
❗注意让求得是煤球的总数,而不是第100层煤球的数量,千万要注意
#include <iostream>
using namespace std;
int n,sum;
int main(){
for (int i = 1; i <= 100; i++)
{
n+=i;
sum+=n;
}
cout<<sum<<endl;
return 0;
}
答案:171700
2.生日蜡烛
某君从某年开始每年都举办一次生日party,并且每次都要吹熄与年龄相同根数的蜡烛。
现在算起来,他一共吹熄了236根蜡烛。
请问,他从多少岁开始过生日party的?
请填写他开始过生日party的年龄数。注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
- 思路一:数据量不大,暴力枚举一个一个试就行。两个for循环,第一个循环表示起始吹蜡烛年数,第二个表示一直吹多少年,不断求和直到sum=236
#include <iostream>
using namespace std;
int main(){
for (int i = 1; i < 100; i++) //从i岁开始吹蜡烛
{
int sum=0;
for (int j = i; j < 100; j++) //从i岁吹到某一岁使得sum=236
{
sum+=j;
if(sum==236)
{
cout<<i<<endl;
break;
}
}
}
return 0;
}
- 思路二:可以看出来年龄其实是等差数列,也可以利用等差数列求和公式计算。前n项和公式为:Sn=n*a1+n(n-1)d/2
其实也是通过枚举a1和n来求解
#include <iostream>
using namespace std;
int main(){
for (int a1 = 1; a1 < 100; a1++)
{
for (int n = 1; n < 100; n++)
{
if((n*a1+n*(n-1)*0.5)==236)
{
cout<<a1<<endl;
break;
}
}
}
return 0;
}
答案:26
3.凑算式
这个算式中AI代表19的数字,不同的字母代表不同的数字。
比如:6+8/3+952/714 就是一种解法,5+3/1+972/486 是另一种解法。
这个算式一共有多少种解法?
注意:你提交应该是个整数,不要填写任何多余的内容或说明性文字。
- 思路一:这题和2015年的第三题三羊献瑞类似。都是全排列问题。
当然如果忘了全排列函数,直接9个for循环也能解,这里就不写这种解题代码了,注意不要都用int类型(int的8/7=1)
#include <bits/stdc++.h>
using namespace std;
double nums[]={1,2,3,4,5,6,7,8,9};
double sum;
int main(){
do
{
double temp=nums[0]+nums[1]/nums[2]+(nums[3]*100+nums[4]*10+nums[5])/(nums[6]*100+nums[7]*10+nums[8]);
if(temp==10)
{
for (int i = 0; i < 9; i++) //输出下所有符合条件的排列情况,便于判断程序对错
{
cout<<nums[i];
}
cout<<endl;
sum++;
}
} while (next_permutation(nums,nums+9)); //不会这个函数的去搜下,这里能排列出1~9所有的排列组合
cout<<sum<<endl;
return 0;
}
- 思路二:和15年第三题一样,这里也可以用dfs进行全排列,这里写下只是为了熟练下dfs,实际上用全排列函数解题速度更快
#include <bits/stdc++.h>
using namespace std;
int res;
bool visited[10];
double nums[10];
bool check(double nums[]) //检查nums数组中的排列是否和为10
{
double temp=nums[0]+nums[1]/nums[2]+(nums[3]*100+nums[4]*10+nums[5])/(nums[6]*100+nums[7]*10+nums[8]);
if(temp==10)
return true;
return false;
}
void dfs(int step)
{
if(step==9&&check(nums)) //如果已经选了9个数并且和为10则res++
{
res++;
return;
}
for (int i = 1; i <= 9; i++)
{
if(!visited[i])
{
visited[i]=true;
nums[step]=i; //将选取的数存到nums数组中,当选好9个数的时候对nums数组中的数求和即可
dfs(step+1);
visited[i]=false;
//nums[step]=0; //解释下这里为什么不用写这句话将nums[step]置0,因为dfs相当于一条路走到黑
//比如之前选了nums[2]=3,那么之后该为nums[3]赋值,nums[4]赋值...也就是这一条路nums[2]已经确定了
//如果函数从dfs(step+1)退出来时,说明nums[2]=3已经用过了,接下来的for循环会重新为nums[2]赋值
}
}
}
int main(){
dfs(0);
cout<<res<<endl;
return 0;
}
4.快速排序
排序在各种场合经常被用到。快速排序是十分常用的高效率的算法。
其思想是:先选一个“标尺”,用它把整个队列过一遍筛子,以保证:其左边的元素都不大于它,其右边的元素都不小于它。
这样,排序问题就被分割为两个子区间。再分别对子区间排序就可以了。
下面的代码是一种实现,请分析并填写划线部分缺少的代码。
#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, 1, N-1);
for(i=1; i<N; i++) printf("%d ", a[i]);
printf("\n");
return 1;
}
- 思路:只要会快排的思想就不难,选定第一个表示a[p]p=0,即第一个元素,i从数组前面向后移,j从数组后面向前移动。
i停在比a[p]大的位置,j停在比a[p]小的位置,交换他俩的位置,到i>=j的时候停止移动,这时候,p位置到j位置是小于a[p]的元素,j+1位置到r位置都是大于a[p]的元素,该段代码的目的是标尺的左边都是小于它的数,右边都是大于它的数,所以要将p位置的元素和j位置的元素进行交换。
答案:swap(a,j,p)
5.抽签
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
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;
}
仔细阅读代码,填写划线部分缺少的内容。
注意:不要填写任何已有内容或说明性文字。
- 思路:查看代码,典型的dfs用到了递归思想(如果不自己调用自己一个输出函数怎么能输出那么多行呢)。
然后答案肯定是f(x,x,x,x)的形式,第一个参数和第四个参数都是数组,一般都不会变动,即f(a,x,x,b)
重点在中间两个参数,这时重点关注递归函数的退出条件
if(kN)也就是if(k6)而刚开始传的k=0,有5个国家,现在k=6就会退出,显然k代表当前选取的国家是哪个(0代表国家A),所以现在可得到f(a,k+1,x,b)
第三个参数肯定和m有关,接着看退出条件if(m==0),也就是说如果前5个国家都选好了人数并且m=0的时候就会输出b。
而m刚开始传入的值是5,显然m表示的是当前还需要再选的人数
观察填空位置附近的代码,第一个for循环表示k国家派的人数为i,第二个for循环表示将派的人数存到b数组中
我们知道m一定是做减操作,i表示派的人数,m表示还需派的人数,所以传入m-i表示选取了i个人后还需再选多少人
这题如果看出来是dfs,就m的判断条件有点难度,如果实在没分析出来,大不了随便多试几次
#include <stdio.h>
#define N 6
#define M 5
#define BUF 1024
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); //如果还需0个人即已经选了5个人则输出b数组
return;
}
for(i=0; i<=a[k]; i++){ //遍历k国家派的人数
for(j=0; j<i; j++) b[M-m+j] = k+'A'; //派出的人存入b数组
f(a,k+1,m-i,b); //填空位置
}
}
int main()
{
int a[N] = {4,2,2,1,1,3};
char b[BUF];
f(a,0,M,b);
return 0;
}
答案:f(a,k+1,m-i,b)
6.方格填数
如下的10个格子
填入0~9的数字。要求:连续的两个数字不能相邻。(左右、上下、对角都算相邻)
一共有多少种可能的填数方案?
请填写表示方案数目的整数。注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
❗题目没说明可不可以填重复的,实际上是不能填重复的,如果可以重复,这题难度会大大增加
- 思路一:全排列
既然是填数,那就可以用全排列,把所有的排列组合列出来然后筛选出符合要求的排列组合
假设从左到右从上到下依次是nums[0]、nums[1]…
在这里检验函数我用了4个if判断,注意要判断特殊情况,如和左边相比时nums[3]不用和nums[2]比较
同样,也可以直接手写一个一个判断
#include<bits/stdc++.h>
using namespace std;
int nums[10]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int res;
bool check(int n)
{
if(n-1>=0&&n-1!=2&&n-1!=6) //和左右的数比较(这里只和左边的比较就可以,因为要对所有数和左边相比,实际上也相当于对右边进行了比较)
if(abs(nums[n]-nums[n-1])==1)
return false;
if(n-4>=0) //和上下比较
if(abs(nums[n]-nums[n-4])==1)
return false;
if(n-5>=0&&n-5!=2) //和左斜比较
if(abs(nums[n]-nums[n-5])==1)
return false;
if(n-3>=0&&n-3!=3) //和右斜比较
if(abs(nums[n]-nums[n-3])==1)
return false;
return true;
}
int main()
{
do
{
int cnt=0;
for(int i=0;i<10;i++) //对每个数都检验下,如果每个数都符合要求则cnt=10
{
if(check(i))
cnt++;
}
if(cnt==10)
res++;
}while(next_permutation(nums,nums+10));
cout<<res<<endl;
return 0;
}
- 思路二:dfs
和全排列函数差不多,只不过是用dfs的方式生成所有排列,在这里判断用的是二维数组,实际上next_permutation函数也可以对二维数组排列
#include <bits/stdc++.h>
using namespace std;
int flag[3][4]; //表示哪些可以填数(flag[0][0]和flag[2][3]不能填)
int mpt[3][4]; //填数
bool visit[10];
int ans = 0;
void init() //初始化
{
int i,j;
for(i = 0 ; i < 3 ; i ++)
for(j = 0 ; j < 4 ; j ++)
flag[i][j] = 1;
flag[0][0] = 0;
flag[2][3] = 0;
}
void Solve() //判断当前dfs所填写的mpt数组是否满足要求
{
int dir[8][2] = { 0,1,0,-1,1,0,-1,0,1,1,1,-1,-1,1,-1,-1}; //表示八个方向
int book = true;
for(int i = 0 ; i < 3 ; i ++)
{
for(int j = 0 ; j < 4; j ++)
{
//判断每个数周围是否满足
if(flag[i][j] == 0)
continue;
for( int k = 0 ; k < 8 ; k ++) //对八个方向遍历判断是否满足题意
{
int x,y;
x = i + dir[k][0];
y = j + dir[k][1];
if(x < 0 || x >= 3 || y < 0 || y >= 4 || flag[x][y] == 0)
continue;
if(abs(mpt[x][y] - mpt[i][j]) == 1)
book = false;
}
}
}
if(book)
ans ++;
}
void dfs(int index)
{
int x,y;
x = index / 4;
y = index % 4;
if( x == 3) //退出条件,表示前3行都已填完
{
Solve();
return;
}
if(flag[x][y])
{
for(int i = 0 ; i < 10 ; i ++)
{
if(!visit[i])
{
visit[i] = true;
mpt[x][y] = i;
dfs(index+1);
visit[i] = false;
}
}
}
else
{
dfs(index+1);
}
}
int main()
{
init();
dfs(0);
cout<<ans<<endl;
return 0;
}
//代码来源:https://blog.csdn.net/y1196645376/article/details/50938608
7.剪邮票
- 思路一:dfs判断是否连通
该思路来源:第七届 蓝桥杯 省赛 第七题 剪邮票
i1、i2、i3、i4和i5为枚举要剪下的5个数,对这五个数构成的连通性进行dfs判断
如果dfs后测得从i1出发从上下左右四个方向上深度优先搜索遍历为i2~i5之间的所有点,cnt标记i5能够走到的是i1~i5这些点的个数
如果cnt == 5就说明是连在一起的
注意从4和8向右行走走不到5和9,并且从5和9出发向左行走走不到4和8~
所以遇到index == 4或者index == 8并且是向右走的时候continue~(index == 5和9并向左走同理~)
将result加一~累加得到的result就是结果116~
#include<bits/stdc++.h>
using namespace std;
int dis[4] = {-1, 1, -4, 4}, visit[13];
int i1, i2, i3, i4, i5, cnt, result = 0;
void dfs(int index) {
if (cnt >= 5) return;
for (int i = 0; i < 4; i++)
{
//如果向右走且当前在4或8 或 向左走当前在5或9
if ((dis[i] == 1 && (index == 4 || index == 8)) || (dis[i] == -1 && (index == 5 || index == 9)))
continue;
int t = index + dis[i];
//如果t在范围内且未访问且t和另外四个数其中一个相等
if (t >= 1 && t <= 12 && visit[t] == 0 && (t == i2 || t == i3 || t == i4 || t == i5)) {
visit[t] = 1;
cnt++;
dfs(t);
}
}
}
int main()
{
//这里最大依次是8、9、10、11、12 即使你都写成12 比如第一个是9,每个for循环初始加1第五个循环就等于 i5=13;i5<=12
for (i1 = 1; i1 <= 8; i1++)
for (i2 = i1 + 1; i2 <= 9; i2++)
for (i3 = i2 + 1; i3 <= 10; i3++)
for (i4 = i3 + 1; i4 <= 11; i4++)
for (i5 = i4 + 1; i5 <= 12; i5++)
{
memset(visit, 0, sizeof(visit));
cnt = 1;
dfs(i1);
if (cnt == 5)
result++;
}
cout << result << endl;
return 0;
}
说明:当时看的时候一直没想明白dfs不是一条路走到黑吗,而像图中最后一种情况这种6和5、10相连有分叉的怎么判断呢?
实际上假设现在dfs走的路径是3、7、6、5此时cnt=4,接下来5向上向下得到的1和9都不是选中的数,下一步会怎样呢?
下一步会回退!5会回退到6的位置,然后6可以到10,此时cnt=5说明是连着的
dfs无法生成图中3、5、6、7、10那种情况所以无法用dfs直接生成满足题意的排列
但是dfs可以用来判断当前排列是否相连
- 思路二:并查集
该思路来源:2016年第七届蓝桥杯【C++省赛B组】【第七题:剪邮票】——并查集,根据个人理解加上了详细注释
实际上还是思路一简单点,但本着学习的态度多学一种思路
一个还不错的并查集讲解视频:【算法】并查集(Disjoint Set)[共3讲]
先用一个结构体记录每个格子的编号、坐标、是否被选,然后选出五个格子,计算这五个格子有多少个独立分块,如果只有一个,说明是连着的,属于一种方案。
#include<bits/stdc++.h>
using namespace std;
int father[12]; //father[i]表示元素i的父亲结点
struct youpiap
{
int x, y, flag; //分别记录行坐标、列坐标、是否被选中
}p[13];
int find(int x) //找根节点
{
if(x == father[x])
return x;
return father[x] = find(father[x]);
}
void join(int n) //合并
{
int xx = p[n].x, yy = p[n].y; //获取n的行列数
int fatherN = find(n); //找到n的根节点
for(int i = 1; i <= 12; i++)
{
if(i == n || p[i].flag == 0) //如果i就是n或者p[i]未被选中则跳过
continue;
//如果p[i]在n的右一 || 左一 || 下一 || 上一 则进行合并
if((p[i].x==xx&&p[i].y==yy+1) || (p[i].x==xx&&p[i].y==yy-1) || (p[i].x==xx+1&&p[i].y==yy) || (p[i].x==xx-1&&p[i].y==yy))
{
father[find(i)] = fatherN; //将i的根节点的父亲设为fatherN 即合并成一个
}
}
}
int main() {
int sum = 0;
for(int i = 0; i < 12; i++) //初始化每个元素的父节点都为自身、每个元素都未被选中
{
father[i] = i;
p[i].flag = 0;
}
int kk = 1;
for(int i = 1; i <= 3; i++) //p[kk]存的是行数和列数
{
for(int j = 1; j <= 4; j++)
{
p[kk].x = i;
p[kk].y = j;
kk++;
}
}
for(int i = 1; i <= 8; i++)
for(int j = i+1; j <= 9; j++)
for(int k = j+1; k <= 10; k++)
for(int q = k+1; q <= 11; q++)
for(int t = q+1; t <= 12; t++)
{
p[i].flag = 1, p[j].flag = 1, p[k].flag = 1, p[q].flag = 1, p[t].flag = 1; //标记被选中
for(int i = 1; i <= 12; i++) //遍历1-12,如果被选中则进行合并
if(p[i].flag)
join(i);
//用集合存放所有被选元素的根节点,若集合只有一个元素(集合无重复元素)则sum++
set<int> s;
for(int i = 1; i <= 12; i++)
if(p[i].flag)
s.insert(find(i));
if(s.size() == 1)
sum++;
//重新初始化
for(int tt = 1; tt <= 12; tt++)
father[tt] = tt;
p[i].flag = 0, p[j].flag = 0, p[k].flag = 0, p[q].flag = 0, p[t].flag = 0;
}
cout << sum << endl;
return 0;
}
8.四平方和
四平方和定理,又称为拉格朗日定理:每个正整数都可以表示为至多4个正整数的平方和。如果把0包括进去,就正好可以表示为4个数的平方和。
比如:
5 = 0^2 + 0^2 + 1^2 + 2^2
7 = 1^2 + 1^2 + 1^2 + 2^2
(^符号表示乘方的意思)
对于一个给定的正整数,可能存在多种平方和的表示法。要求你对4个数排序:0 <= a <= b <= c <= d并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法
程序输入为一个正整数N (N<5000000)要求输出4个非负整数,按从小到大排序,中间用空格分开
例如,输入:
5
则程序应该输出:
0 0 1 2
再例如,输入:
12
则程序应该输出:
0 2 2 2
再例如,输入:
773535
则程序应该输出:
1 1 267 838
- 思路一:暴力枚举 (超时)
这道题猛地一看很简单不就是四个for循环枚举就行了,但是注意数据范围(N<5000000),直接for循环枚举肯定超时(可以找在线oj试一下)
下面代码不是最终答案,仅仅是一种思路,毕竟即使超时也能拿到一半多的分
可以优化下,用3个for循环,对于i4拿N减去前面的数看看有无可能存在i4,这里就不写了,这样也会超时
#include<bits/stdc++.h>
using namespace std;
int N;
int main() {
cin>>N;
for(int i1=0;i1*i1<=N;i1++)
for(int i2=i1;i1*i1+i2*i2<=N;i2++)
for(int i3=i2;i1*i1+i2*i2+i3*i3<=N;i3++)
for(int i4=i3;i1*i1+i2*i2+i3*i3+i4*i4<=N;i4++)
{
if(i1*i1+i2*i2+i3*i3+i4*i4==N)
{
cout<<i1<<" "<<i2<<" "<<i3<<" "<<i4<<endl;
return 0;
}
}
return 0;
}
- 思路二:二分法
实际上这是二分法的一个变种,大致思路是先枚举可能的c和d,将 c 2 + d 2 c^2+d^2 c2+d2存起来降低时间复杂度并排序,然后枚举a和b再二分查找c和d的平方和
代码来源:第七届蓝桥杯 ——四平方和
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2500010;
struct Sum
{
int s, c, d;
bool operator < (const Sum &t) const //重载小于号
{
if(s != t.s) return s < t.s;
if(c != t.c) return c < t.c;
return d < t.d;
}
}p[N];
int n, k;
int main()
{
int n;
cin >> n;
for (int c = 0; c * c <= n; c ++)
for (int d = c; c * c + d * d <= n; d ++)
p[k ++] = {c * c + d * d, c, d};
sort(p, p + k);
for (int a = 0; a * a <= n; a ++)
{
for (int b = a; a * a + b * b <= n; b ++)
{
int t = n - a * a - b * b;
int l = 0, r = k - 1;
while(l < r) //二分思想
{
int mid = l + r >> 1; //除2
if(p[mid].s >= t)
r = mid; //找到第一个大于等于 t 的位置
else
l = mid + 1;
}
if(p[l].s == t)
{
printf("%d %d %d %d\n", a, b, p[l].c, p[l].d);
return 0;
}
}
}
}
9.交换瓶子
有N个瓶子,编号 1 ~ N,放在架子上。
比如有5个瓶子:2 1 3 5 4
要求每次拿起2个瓶子,交换它们的位置。经过若干次后,使得瓶子的序号为:1 2 3 4 5
对于这么简单的情况,显然,至少需要交换2次就可以复位。
如果瓶子更多呢?你可以通过编程来解决。
输入格式为两行:第一行: 一个正整数N(N<10000), 表示瓶子的数目第二行:N个正整数,用空格分开,表示瓶子目前的排列情况。
输出数据为一行一个正整数,表示至少交换多少次,才能完成排序。
例如,输入:
5
3 1 2 5 4
程序应该输出:
3
再例如,输入:
5
5 4 3 2 1
程序应该输出:
2
- 思路一:找规律
本以为放在第九题会用到稍微复杂点的算法,后来发现不是
刚开始没有思路,后来经过观察发现,比如5 4 3 2 1,这里nums[1]=5,而nums[5]刚好等于1,这样相当于交换1次
又如3 1 2 5 4,nums[1]=3,而nums[3]=2,nums[2]=1,你应该发现规律了,从1出发找nums[i]和i不等就接着往下找直到找到nums[x]=i
这里3、1、2进行两次交换就可以,5和4进行1次交换就可以
看网上的题解大多都是思路二那种直接swap,可能我脑回路比较奇特吧 😹
本题在线OJ:1224. 交换瓶子
#include<bits/stdc++.h>
using namespace std;
int N,res;
int main() {
cin>>N;
int nums[N+1];
bool visited[N+1];
for(int i=1;i<=N;i++)
cin>>nums[i];
for(int i=1;i<=N;i++)
{
if(!visited[i])
{
int m=i,n=i;
while(1)
{
if(n!=nums[m]) //若当前位置的nums不等于起始的i
{
visited[m]=1; //标记成已访问
m=nums[m]; //把nums[m]赋值给m,如示例中的m=nums[1]=3,然后if判断n是否等于nums[3]
res++;
}
else
{
visited[m]=1; //注意如果n和nums[m]相等要把m也标记下,避免重复计算
break;
}
}
}
}
cout<<res<<endl;
return 0;
}
- 思路二:暴力枚举swap
这是看别人题解的方法,思路一其实并没有交换数据只是求了下结果,也可以对于位置i,遍历之后的数找到j使得nums[j]=i,然后交换nums[i]和nums[j]
该题解原地址:AcWing 1224. 交换瓶子
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int a[N],n,t;
int ans=0;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
if(a[i]!=i)
{
for(int j=i+1;j<=n;j++) //遍历i+1到n找到a[j]和i相等然后a[i]和a[j]交换
{
if(a[j]==i)
{
swap(a[i],a[j]);
}
}
ans++;
}
}
cout<<ans<<endl;
return 0;
}
10.最大比例
X星球的某个大奖赛设了M级奖励。每个级别的奖金是一个正整数。并且,相邻的两个级别间的比例是个固定值。也就是说:所有级别的奖金数构成了一个等比数列。比如:16,24,36,54其等比值为:3/2
现在,我们随机调查了一些获奖者的奖金数。请你据此推算可能的最大的等比值。
输入格式:第一行为数字N,表示接下的一行包含N个正整数第二行N个正整数Xi(Xi<1 000 000 000 000),用空格分开。每个整数表示调查到的某人的奖金数额
要求输出:一个形如A/B的分数,要求A、B互质。表示可能的最大比例系数
测试数据保证了输入格式正确,并且最大比例是存在的。
例如,输入:
3
1250 200 32
程序应该输出:
25/4
再例如,输入:
4
3125 32 32 200
程序应该输出:
5/2
再例如,输入:
3
549755813888 524288 2
程序应该输出:
4/1
- 思路一:转化为求相邻比值的最大公约数。
思路来源:2016蓝桥杯省赛第十题:最大比例
网上很多用的辗转相减法,但基本都没有具体解释,这里给出我自己的理解
先解释下代码:大致思路就是先排序,然后每个数除第一个数约分成一个不能再约分的分数保存起来
对这些分数的分子和分母依次用辗转相减法,注意并不是求他们的最大公约数
举个 🌰 ,第二个示例中,先排序得到32、32、200、3125,然后用每个数除第一个数得到不能再约分的分数(注意去重)
即 25 / 4 25/4 25/4和 3125 / 32 3125/32 3125/32,然后对得到的每个分数的分子和分母的指数求最大公约数
如4和32不是求4和32的最大公约数,而是4= 2 2 2^2 22,32= 2 5 2^5 25。它们的指数分别为2和5,对2和5求最大公约数即1则函数返回 2 1 2^1 21,对应上图解释中的返回 p k p^k pk
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=110;
int n;
LL x[N],a[N],b[N];
LL gcd(LL a,LL b)//求最大公约数
{
return b?gcd(b,a%b):a;
}
LL gcd_sub(LL a,LL b)//辗转相减,返回p的k次方
{
if(b>a)
swap(a,b);
if(b==1)
return a;
return gcd_sub(b,a/b);
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>x[i];
sort(x,x+n);
LL dd=0;
int cnt =0;
for(int i=1;i<n;i++)
{
if(x[i]!=x[i-1])//去重
{
dd =gcd(x[i],x[0]);//最大公约数
a[cnt] = x[i]/dd;
b[cnt] = x[0]/dd;
cnt++;
}
}
LL up = a[0],down = b[0];//up分子 down分母
for(int i=1;i<cnt;i++)//分开求分子分母的指数最大公约数
{
up = gcd_sub(up,a[i]);
down = gcd_sub(down,b[i]);
}
cout<<up<<"/"<<down;
return 0;
}
注意这里的辗转相减法是基于原本思想的一个变形,真正的辗转相减法(更相减损术)代码如下
//这个是求最大公约数的
ll gcd(ll a,ll b)
{
if(a==b) return a;
return a>b?gcd(a-b,b):gcd(b-a,a);
}
- 思路二:分数相除
思想来源:2016蓝桥杯A组第十题 最大比例
这也是一种方法,比较独特,这里贴上这位博主的解释,自行理解吧
先对输入数据排序,然后依次用后一个除以前一个得到比例,当然这个比例要用结构体表示(分子,分母),将所有比例保存下来
对比例排序,然后用后一个比例除以前一个比例,更新后一个比例为所求值
所有比例遍历完,对比例排序,按照相同的操作循环,直到前面所有比例为1:1,最后一个比例即为所求比例;
说起来有点抽象,举个例子,假如所求比例为t,比例数组 1 ,t2,t,t6,t^3,排序之后 1,t2,t3,t^6
相邻比例相除:1,t2,t,t5,排序:1,t,t2,t5;比例相除:1,t,t,t4;再相除:1,t,1,t4…
最终会得到1,1,1,t;
#include<iostream>
#include<algorithm>
using namespace std;
long long gcd(long long a, long long b)
{
return a%b ? gcd(b, a%b) : b;
}
struct bili
{
int fenzi;
int fenmu;
bool operator<(bili&t)const
{
return fenzi*1.0 / fenmu < t.fenzi*1.0 / t.fenmu;
}
}n[100];
int main()
{
long long a[100];
int count, i;
cin >> count;
for (int i = 0;i < count;i++)
cin >> a[i];
sort(a, a +count);
for (i = count - 1;i > 0;i--)
{
long long t = gcd(a[i], a[i - 1]);
n[i - 1].fenzi = a[i] / t, n[i - 1].fenmu = a[i - 1] / t;
}
sort(n, n + count - 1);
while (1) {
for (i = 0;i < count - 2;i++)
{
int t = gcd(n[i + 1].fenzi *n[i].fenmu, n[i + 1].fenmu*n[i].fenzi);
n[i + 1].fenzi = n[i + 1].fenzi *n[i].fenmu / t;
n[i + 1].fenmu = n[i + 1].fenmu *n[i].fenzi / t;
}
sort(n, n + count - 1);
for (i = 0; n[i].fenzi == 1;i++);
if (i == count - 2)
break;
}
cout << n[count - 2].fenzi << '/' << n[count - 2].fenmu;
}