2022蓝桥杯国A
《A填空问题》
数学前提:错排问题
结论:设C(n)为有n个人都错排了的方案数
C(n)= (n-1)(C(n-1)+C(n-2))
阶乘结论:n! 当n>20时 会爆long long
数学前提:康拓展开 cantor
是用来求全排列中 某一个排列在整个全排列的位数 (即这个排列在全排列中是第几小)
结论:∑sum(ai)* (n-i)!
sum(ai)是比ai小的有多少个元素(除去已经固定的,不能再用的)
函数:reverse(str.begin(),str.end()) 常用来反转字符串
#include <iostream>
#include <algorithm>
#include <cstring>
#include <assert.h>
using namespace std;
typedef long long ll;
ll C[20];
ll q1()
{
C[1] = 0, C[2] = 1;
for (int i = 3; i <= 14; i++)
C[i] = (ll)(i - 1) * (C[i - 1] + C[i - 2]);
ll t = 1;
for (ll i = 28, j = 2; i >= 15; i--)
{
while (j <= 14 && t % j == 0)
{
t /= j;
j++;
}
t *= i;
}
return t * C[14];
}
ll fac[20];
//用来求这个排列是在全排列中的第几小
ll cantor(string a)
{
ll na = 0;
for (int i = 0; i < a.length(); i++)
{
ll sum = 0;
for (int j = i; j < a.length(); j++)
if (a[i] > a[j])
sum++;
na += sum * fac[17 - i - 1];
}
// 因为前面有na个比这个排列小,则这个排列排在第na+1位
na++;
return na;
}
ll q2()
{
string a = "aejcldbhpiogfqnkr";
string b = "ncfjboqiealhkrpgd";
string c = "abcdefghijklnopqr";
reverse(c.begin(), c.end());
fac[1] = 1;
for (ll i = 2; i <= 17; i++)
fac[i] = fac[i - 1] * i;
ll na = 0, nb = 0, nc = 0;
na = cantor(a), nb = cantor(b), nc = cantor(c);
ll ans = min(nc - nb + na, nb - na);
return ans;
}
int main()
{
string ans[] = {
"1286583532342313400",
"106148357572143",
};
char T;
cin >> T;
cout << ans[T - 'A'] << endl;
return 0;
}
《D最大公约数》
这个问题告诉了我:
首先从暴力开始想起,如果暴力都不会,那么后面也不用想了,直接跳过吧
这道题暴力的话:
首先我们很容易想到:如果让这个数组中出现了一个1,那么接下来都是只要一次操作,就可以将一个数变成1
这是最优的做法
现在问题变成了:如何最快让数组中出现1?
暴力地从一个数组中的下标开始,然后一直对其相邻的数求gcd,然后替换
然后接着对下一个数进行gcd
(之所以可以朝着一个方向进行是因为gcd满足交换律 gcd(gcd(a,b),c)=gcd(a,gcd(b,c)))
直到出现1为止
即 两重for 循环
特殊情况:数组中原本就有1,则记录下1的个数cnt,然后cout<<n-cnt
如果没有1出现,那么一定这个数组不能只含1了,cout<<-1
两重for循环是可以优化的
将思路转化一下:想要求最快使数组出现1,是不是等于找一个最短的区间使这个区间的gcd==1?
上面我们两重for循环其实就是干的这个事情
但是有很多区间我们重复计算了
我们可以用线段树来预处理一下一个区间的gcd,当我们要的时候直接log的速度计算出
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 2;
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
int n, num[N];
struct tree
{
int l, r, ans;
} tr[N << 4];
void pushup(int k)
{
tr[k].ans = gcd(tr[k << 1].ans, tr[k << 1 | 1].ans);
}
void build(int k, int l, int r)
{
tr[k].l = l, tr[k].r = r;
if (l == r)
{
tr[k].ans = num[l];
return;
}
int mid = l + r >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
pushup(k);
}
int query(int k, int l, int r)
{
if (tr[k].l >= l && tr[k].r <= r)
return tr[k].ans;
int mid = (tr[k].l + tr[k].r) >> 1;
// l>mid 表示目标区间完全在线段树分段的右边
// r<=mid 表示目标区间完全在线段树分段的左边
// 剩下的是要处理的又在左边和在右边的
if (l > mid)
return query(k << 1 | 1, l, r);
else if (r <= mid)
return query(k << 1, l, r);
else
return gcd(query(k << 1 | 1, l, r), query(k << 1, l, r));
}
bool check(int mid)
{
// 但是有个严重的问题:
// 普通地算出一个区间的gcd实在是太慢了,要O(n)的时间
// 想一下当我们要算出一个区间的gcd时是不是本来算出来的但是很多我们又要算一遍?
// 这是因为gcd这个区间不像+-*/法一样,当其中一个元素加进来或减掉了就可以简单地算出
// gcd是不能的
// 所以我们有必要用一个数据结构将我们已经算出的某一个区间的gcd保存下来
// 当要使用时可以拿出来用
// 线段树!
bool flag = false;
for (int i = 1; i <= n - mid + 1; i++)
{
if (query(1, i, i + mid - 1) == 1)
{
flag = true;
break;
}
}
return flag;
}
void solve()
{
cin >> n;
int cnt = 0;
for (int i = 1; i <= n; i++)
{
cin >> num[i];
if (num[i] == 1)
cnt++;
}
if (cnt)
{
cout << n - cnt << endl;
return;
}
build(1, 1, n);
// 这个题目优化的话相当于寻找一个最小的区间,使这个区间的gcd为1
// 这个区间我们可以二分进行查找
int l = 2, r = n, ans = -1;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid))
{
r = mid - 1;
ans = mid;
}
else
l = mid + 1;
}
if (ans == -1)
cout << ans << endl;
else
cout << ans - 1 + n - 1 << endl;
}
int main()
{
solve();
return 0;
}
我决定这道题目的关键是:
将不断地gcd替换直到出现1,转化成找一个最小的区间使得这个区间gcd==1的思想转化
《G选素数》
我们要想清楚一件事:
x>p , n=kp
x=n-p+1
这里的p是2~n中的素数
为啥x等于这玩意?
1.n是素数p的倍数,当x不断++可以到n,那么x一定是>=n-p+1的
如果n<n-p+1 那么 到不了n,而是到n-p了
因为如果n是p的倍数,那么n-p也是
那么xmin=n-pmax+1, xmax=n
pmax是2~n中合法的最大素数
啥是合法的?即 xmin> n-p+1
于是先暴力的做:
我们已经知道n
那也知道x2=n-p+1
x2max=n
x2min=n-pmax+1
枚举x2
然后在x2固定的情况下
我们可以知道x=x2-pmax+1
那么问题主要变成了如何快速知道2~n中最大的素数?
有个暴力的想法(不能得满分)
我们用线性筛求出2~n中的全部素数
然后就可以知道最大的素数了
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <assert.h>
#include <vector>
using namespace std;
typedef long long ll;
const int INF = 1e6 + 2;
int n, ans = INF;
int prime(int maxn)
{
bool st[INF];
memset(st, false, sizeof(st));
vector<int> pr;
for (int i = 2; i <= maxn; i++)
{
if (!st[i])
pr.push_back(i);
for (int j = 0; j < pr.size() && pr[j] * i <= maxn; j++)
{
st[pr[j] * i] = true;
if (i % pr[j] == 0)
break;
}
}
if (pr.size() <= 0)
return INF;
else
{
int res = -1;
for (int j = 0; j < pr.size(); j++)
{
if (maxn % pr[j] == 0 && maxn - pr[j] + 1 > pr[j])
res = max(res, pr[j]);
}
if (res == -1)
return INF;
else
return res;
}
}
bool solve()
{
cin >> n;
// 先找到2~n中最大的素数,确定范围
int mnp = prime(n);
if (mnp >= INF)
return false;
int l = n - mnp + 1, r = n;
for (; l <= r; l++)
{
int n1 = l;
mnp = prime(n1);
if (mnp >= INF)
continue;
ans = min(ans, n1 - mnp + 1);
}
if (ans >= INF)
return false;
else
return true;
}
int main()
{
if (!solve())
cout << -1 << endl;
else
cout << ans << endl;
return 0;
}
《其他题》
完全会写 或 完全不会写(只会暴力)
国A太难了!
本文作者:次林梦叶
本文链接:https://www.cnblogs.com/cilinmengye/p/17383515.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2022-05-08 map令人惊奇的作用
2022-05-08 最小生成树----特殊已经有路产生要我们链接未有路的