音无结弦之时,天使跃动之心。立于浮华之世,奏响天籁之音。.|

次林梦叶

园龄:3年3个月粉丝:22关注:3

2022蓝桥杯国A

《A填空问题》

数学前提:错排问题

结论:设C(n)为有n个人都错排了的方案数

C(n)= (n-1)(C(n-1)+C(n-2))

阶乘结论:n! 当n>20时 会爆long long 

 

数学前提:康拓展开 cantor

是用来求全排列中 某一个排列在整个全排列的位数 (即这个排列在全排列中是第几小)

具体:资料1资料2

 

结论:∑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 中国大陆许可协议进行许可。

posted @   次林梦叶  阅读(33)  评论(0编辑  收藏  举报
历史上的今天:
2022-05-08 map令人惊奇的作用
2022-05-08 最小生成树----特殊已经有路产生要我们链接未有路的
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起