蓝桥杯第十三届

1.小蓝与钥匙

答案:1286583532342313400

小蓝是幼儿园的老师,他的班上有28 个孩子,今天他和孩子们一起进行了一个游戏。
小蓝所在的学校是奇宿制学校,28 个孩子分别有一个自己的房间,每个房间对应一把钥匙,每把钥匙只能打开自己的门。现在小蓝让这 28 个孩子分别将自己宿舍的钥匙上交,再把这28 把钥匙随机打乱分给每个孩子一把钥匙,有28!=28 x 27 × …•x 1种分配方案。小蓝想知道这些方案中,有多少种方案怡有一半的孩子被分到自己房间的钥匙(即有14 个孩子分到的是自己房间的钥匙,有14 个孩子分到的不是自己房间的钥匙)。

解题思路:

是一个全错排问题,在28个里面任意选择14个 C(28,14),乘以全错排D(14);

全错排公式 D(n)=(n-1)*(D(n-1)+D(n-2))

公式推导:【组合数学】错排问题 ( 递推公式 | 通项公式 | 推导过程 ) ★_错排问题递推公式-CSDN博客

计算C(28,14)时,可采用公式 C(n,k)=C(n-1,k-1)+C(n-1,k) 计算; 直接计算long long直接承载不了

使用C++解题代码:

#include<iostream>
using namespace std;

unsigned long long D(long a)   //错排问题
{
    if (a == 0 || a == 1)
        return 0;
    if (a == 2)
        return 1;
    else
        return ((a - 1) * (D(a - 1) + D(a - 2)));
}
//计算组合数字
long calcom(int n,int k)
{
    if (k == 0 || k == n)
        return 1;
    else
        return calcom(n - 1, k - 1) + calcom(n - 1, k);
}


int main()
{
    unsigned long long b=D(14);   //使用unsigned long long时得到正确答案
    long a = (calcom(28,14));
    unsigned long long c = a * b;
    cout << c ;
}

2.排列距离

答案:106148357572143

解题思路:主要难点在于如何得到两个排列的顺序,是一个经典的全排列的康托展开。康托展开的公式是:X=A[i]*(n-1)!+A[i+1]*(n-2)!+.......+A[n-1]*1!+A[n]*0!  其中A[i]是第i个数后面比第i个数小的个数。 计算出来数字后,只需要对比min(b-a,17!-(b-a))就可以得到。

逆康托展开:

康托展开和逆康托展开_康托展开与逆康托展开-CSDN博客

代码如下:

//主要难点在于康托展开

#include<iostream>
using namespace std;
#include<string>

unsigned long long factorial[20];  //存放阶乘
void get_fact(string str)     //获取阶乘
{
    factorial[0]=factorial[1] = 1;
    int n = str.size();
    for (int i = 2; i <= n; i++)
        factorial[i] =factorial[i-1] *i;
    return;
}

//康托展开
unsigned long long cantor(string str)
{
    unsigned long long res = 1;
    int len = str.length();  //计算输入进来的 aejcldbhpiogfqnkr和ncfjboqiealhkrpgd的长度;
    for (int i = 0; i < len; i++)
    {
        int temp = 0;
        for (int j = i + 1; j < len; j++) //从i的后一位开始计算,内循环的作用是记录后面比这一位小的数字
        {
            if (str[i] > str[j])    //符合康托展开的条件
                temp++;
        }
        res += temp * factorial[len - i - 1];
        /*康托展开的公式:   X = An * (n - 1)! + An-1 * (n - 2)! + …… + A2 * 1! + A1 * 0!
        An 是第 i 位后面的数中比其小的数的个数*/

    }
    return res;
}

int main()
{
    get_fact("aejcldbhpiogfqnkr");
    unsigned long long a=cantor("aejcldbhpiogfqnkr");
    unsigned long long b = cantor("ncfjboqiealhkrpgd");
    unsigned long long c= factorial[17];
    unsigned long long ans = min(factorial[17]-b+a,b-a);
    cout << ans;
}

优化版本:

#include<iostream>
using namespace std;

long long jiecheng(int a) {
	if (a == 0 || a == 1)
		return 1;
	return a * jiecheng(a - 1);
}
long long kantuo(string s) {
	int n = s.size();
	long long ans = 0; //答案
	for (int i = 0; i < n; i++) {
		int count = 0;
		for (int j = i + 1; j < n; j++) {
			if (s[j] < s[i])
				count++;
		}
		ans += count * jiecheng(n - i - 1);
	}
	return ans + 1;
}

int main() {
	string a = "aejcldbhpiogfqnkr";
	string b = "ncfjboqiealhkrpgd";
	long long c = kantuo(a);
	cout << c << endl;
	long long d = kantuo(b);
	long long res = min(d - c, jiecheng(17) - (d - c));
	cout << res << endl;
}

3.13省A  裁纸刀

4+19+20*21=443;

4.13省A  灭鼠先锋

手搓版(B站):谁先下第二行谁输(下一个,则下俩;下俩则下一个);转换为第一行,模拟一下

结果为:LLLV;

5.13省A 求和

(1)暴力法:(能通过60%)

#include <iostream>
using namespace std;
#include<vector>

long long add(vector<long long> a)
{
    long long sum = 0;
    for (int i = 0; i < a.size() - 1; i++)
    {
        for (int j = i + 1; j < a.size(); j++)
        {
            sum += a[i] * a[j];
        }
    }
    return sum;
}

int main()
{
    vector<long long> a;
    long long n;
    cin >> n;
    cout << endl;
    for (int i = 0; i < n; i++)
    {
        int num;
        cin >> num;
        a.push_back(num);
        cout << " ";
    }
    long long sum = add(a);
    cout << endl << sum << endl;
    return 0;
}

(2)使用前缀和优化(100%)

思路:

可以进行如下转换:

#include<iostream>
using namespace std;
typedef long long ll;
const ll N=2*1e5+10;
ll f[N];

int main(){
	long long n;
	cin>>n;
	ll sum[N]={0};
	ll S=0;
	for(int i=1;i<=n;i++){
		cin>>f[i];
		sum[i]=sum[i-1]+f[i];  //前缀和 
	}
	for(int i=1;i<=n;i++){
		S+=f[i]*(sum[n]-sum[i]);
	} 
    cout<<S<<endl;
    return 0;
} 

6.13省研究生组  质因数个数

思路:主要是数学知识,一个数字其实是由质因数的乘积组成的

看这个:[蓝桥杯2022初赛] 质因数个数-CSDN博客

代码:

#include<iostream>
using namespace std;
#include"math.h"

//思路:所有的数都是质因数(次方)的乘积   注意:1不是质数
long long check(long long n)
{
    int num=0;
    for (int i = 2; i < sqrt(n); i++)
    {
        if (n % i == 0)
            num++;
        while (n % i == 0)   //把所有这个除数的数字除掉  
            //因为所有约数都是质因数的乘积,怎么除都不可能先除掉没除过的质因数
            n /= i;
    }
    if (n > 1)    //这步操作 比如  14=2*7;由于是上面是平方判断不到n为7的情况,
        //这种情况下如果最后剩余的n大于一,代表没除完,还有一个质数,加上即可
        num++;
    return num;
}

int main()
{
    long long n, m;  //n为输入数;m为输出数
    cin >> n;
    m = check(n);
    cout << m;
}

7.13省研究生  GCD

思路:gcd(a,b)=gcd(a,a-b)    (gcd是最大公约数)

代码:

#include<iostream>
using namespace std;
typedef long long ll;

//主要是数学思路;注意公约数有一个定理 gcd(a,b)=gcd(b,a%b);  gcd(a,b)=gcd(a,a-b);
// g(a,b)=g(a,a-b)     gcd(a+k,b+k)=gcd(a+k;a-b);令a-b=c;由于a和b固定,所以c也是固定的;
//c为固定,a+k不固定;要想达到gcd(a+k,c)最大,只需令gcd(a+k,c)=c;所以问题转化为最小的k使得a+k为c的倍数
//只需要满足k=c-a%c;  解释:a%c+k=0或者c时 a+k为c的倍数  因为k为正整数,所以a%c+k=c-->k=c-a%c

ll gcd(ll a, ll b)
{
    ll k = 0;
    k = (b - a) - a % (b - a);
    return k;
}

int main()
{
    ll a, b;  //输入 a,b
    cin >> a;
    cout << " ";
    cin >> b;
    ll k;
    k = gcd(a,b);
    cout << k << endl;
    return 0;
}

8.13省A 青蛙过河

思路:显然,在每一个y(跳跃能力)区间上,青蛙绝对要跳一次,这样的情况下,只需要将每一个长度为y的区间的高度和大于2x,则一定可以满足条件,这样只需要从y=1到n去找能跳过去的最小的y就可以

代码:

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int sum[N] ;
typedef long long ll;
ll n, x; //n为河宽,x为上学的天数,2x为过河次数
/*
青蛙过河:
思想:通过使得每一个y区间的石块高度和大于2x
*/

bool check(int y) {
    for (int i = 0; i +y< n ; i++) {
        if (sum[i + y] - sum[i] < 2 * x)
            return false;
    }
    return true;
}


int main(){
    cin >> n >> x;
    int a = 0;  //每一次输入的石块高度
    sum[0] = 0;
    for (int i = 1; i < n; i++) {
        cin >> a;
        sum[i] = sum[i - 1] + a;     //记录录入石块的前缀和
    }
    int y;//记录跳跃能力
    for (int i =1; i <n+1; i++) {
        if (check(i)) {
            y = i;
            break;
        }    
    }
    cout << y;
    return 0;
}

9.13研究生  全排列的价值

思路:主要是动态规划思想 

动态规划求解步骤:穷举分析,确定边界,找规律、确定最优子结构,写出状态转移方程。

本题:  蓝桥杯2022年第十三届省赛真题-全排列的价值_对于一个排列 a = (a1, a2, · · · , an),定义价值 ci 为 a1 至 ai -CSDN博客

推荐看这一篇,f(n)主要是考虑将第一位数固定的情况下,其余的数组成的排列其实就是一个f(n-1)的情况,所以按照这样

f[n]=f[n-1]*n+n*(n-1)/2*(n-1)!

解释一下 这个状态转移方程   第一个n是 第一位数的固定有n种情况(1,2,3,......n),后面的(n-1)!其实就是每一个第一位数放置时候的情况数,(1放置的时候有(n-1)!种情况)

代码如下:

#include<iostream>
using namespace std;
const int N = 1e6;
#define mod 998244353;
typedef long long ll;

int main() {
    int n;
    cin >> n;
    ll f[N] = { 0 };
    f[1] = 0; f[2] = 1;
    ll jie = 2;
    ll num = 3;
    for (int i = 3; i <= n; i++) {
        f[i] = (i * f[i - 1] + num * jie) % mod;
        jie = (jie * i) % mod;  //计算阶乘   注意不要写 jie*=i%mod,计算顺序问题
        num = (num + i) % mod;//计算求和 
    }
    cout << f[n];
    return 0;
}

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值