2016年第七届蓝桥杯C/C++B组省赛题目解析

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;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值