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))就可以得到。
逆康托展开:
代码如下:
//主要难点在于康托展开
#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省研究生组 质因数个数
思路:主要是数学知识,一个数字其实是由质因数的乘积组成的
代码:
#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;
}