【算法比赛】竞码编程-蓝桥杯校内选拔赛(决赛)重现赛

部分题目有所难度,特别是最后一题,要得到满分 25 ,还是很难的

 

比赛:

http://oj.hzjingma.com/contest/status?id=73


试题A:好运2020(暴力枚举)

试题B:信息加密(简单题)

试题C:方圆(判断位置)

试题D:数据压缩(字符串模拟)

试题E:n项和(取模运算)

试题F:掉发警告(规律题)

试题G:体前屈大赛(思维)

试题H:数列第N项(数学 + 快速幂 或者 矩阵快速幂)

试题I:耗时种数(最短路)

试题J:传递信息(思维 + 最短路 + 大数乘法快速幂)


试题A:好运2020(暴力枚举)

 

根据题目,枚举任何一个数的质因子个数。我们可以先将范围内的质数求出,然后对于某一个数,求其质因子个数时,枚举每一个质数(比这个数小的范围内),看看是不是它的因子即可。

答案是:1937

#include <bits/stdc++.h>
using namespace std;

int p[4000];

void init()  // 求出范围内的所有质数
{
    memset(p, 0, sizeof(p));
    for(int i = 2;i < 3000; ++i)
    {
        if(p[i] == 0)
        {
            for(int j = 2 * i; j < 3000; j += i)
                p[j] = 1;
        }
    }
}


bool check(int num)  // 对某一个数,求其质因子个数
{
    int cnt = 0;
    for(int i = 2; i <= num; ++i)
    {
        if(p[i] == 0)  // 枚举每一个质数,判断是不是这个数的因子
        {
            if(num % i == 0) ++cnt;
        }
    }
    if(cnt != 4) return true;
    else return false;
}

int main()
{
    init();

    int ans = 1;   // 由于范围是 [1, 2020],但是对于 1,质因子个数为 0,质数是从 2 开始的,枚举范围内每一个数,从 2 开始,那么数字 1 ,质因子个数是0,所以相当于答案 + 1.

    for(int i = 2;i <= 2020; ++i)  
    {
        if(check(i))
        {
            ++ans;
        }
    }
    cout << ans << endl;
    return 0;
}

 


试题B:信息加密(简单题)

 

明文" ABCDEFGHIJKLMNOPQRSTUVWXYZ"

其密文 是"JKLMNOPQRSTUVWXYZABCDEFGHI"。之间的关系就是,对于明文而言,对应的密文就是 : 字母 + 9 。那么对于从密文到明文,如果密文是 c,那么明文就是: c - 'A' - 9 + ’A' (如果 c - 'A' - 9 < 0 ,那就加上 26 即可)

 

#include <bits/stdc++.h>
using namespace std;

int main()
{
    string s;
    cin >> s;
    int n = s.size();

    int d = 'J' - 'A';  // 从明文到密文,是 + d,那么从密文到明文,就是 - d

    for(auto& c : s)
    {
        c = ((c - 'A') - d + 26) % 26 + 'A';
    }
    cout << s << endl;
    return 0;
}

 


试题C:方圆(判断位置)

 

要求,矩形和圆的位置关系,其实可以转换为,求圆和矩形的每一条边的关系。

圆与边的关系,就是,求圆心到边的距离(由于这道题的矩形四条边都是平行于坐标轴的,所以很好求)与 圆 半径的关系。

但是还要注意的一点,边的有范围的,当我们判断,圆与边是相交或者相切时,还要多一个条件,即圆心位置,在对应边的范围内,不然可能出现以下情况:

虽然这个会判定为圆与边相切,但是对于圆和矩形的关系,其实相离的

因此对于圆与边相切或者相交的时候,还要加上一个限制条件,即圆心的坐标(x 或 y)要在边的范围中才行,这样子对于圆与矩形的关系才是相切或相交。

 

#include <bits/stdc++.h>
using namespace std;

bool check(int d, int r)  // 判断圆与边的关系
{
    if(d <= r)
    {
        return true;
    }
    return false;
}

int main()
{
    int x0, y0, r, px, py, w, h;
    cin >> x0 >> y0 >> r >> px >> py >> w >> h;

    if(check(abs(y0 - py),r) && (x0 >= px && x0 <= px + w))  // 当圆与矩形下面那条边判断是相切或相交时,还要求,其 x0 范围,在下面边的 x 范围中(下面的类似)
        puts("yes");
    else if(check(abs(y0 - py - h),r) && (x0 >= px && x0 <= px + w))
        puts("yes");
    else if(check(abs(x0 - px),r) && (y0 >= py && y0 <= py + h))
        puts("yes");
    else if(check(abs(x0 - px - w),r) && (y0 >= py && y0 <= py + h))
        puts("yes");
    else
        puts("no");

    return 0;
}

 


试题D:数据压缩(字符串模拟)

 

字符串模拟题,我们记录上一个字符为 cur,这个字符出现次数 cnt

  • 如果当前字符 c == cur,那么 该字符连续出现次数 ++cnt
  • 如果当前字符 c != cur,那么相当于前面连续相同的字符已经结束,那么根据是哪个字符 cur,和对应出现次数 cnt,压缩即可。然后 此时,新的  cur = c,cnt = 1

 

#include <bits/stdc++.h>
using namespace std;

int main()
{
    string s;
    cin >> s;
    int n = s.size();

    string res = "";
    int cnt = 1;
    char cur = s[0];

    for(int i = 1;i < n; ++i)
    {
        if(s[i] == cur)
        {
            ++cnt;
        }
        else
        {
            res += cur;
            res += to_string(cnt);
            cnt = 1;
            cur = s[i];
        }
    }
    res += cur;
    res += to_string(cnt);

    cout << res << endl;
    return 0;
}

试题E:n项和(取模运算)

根据题目,其实就是一个等差数列求和,即 \frac{n(n + 1)}{2},根据数据范围,n 是 10^18,用 long long 存储,那么根据取模运算  (a * b) % MOD = (a % MOD) * (b % MOD) % MOD。但是其中还有 / 2,关于除法的取模运算,用到逆元(同时需要用快速幂求)。这里我为了简单,那么通过先将 / 2 运算,那么就需要根据 n 是奇数偶数分不同情况来做即可。

 

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const LL MOD = 1e9 + 7;

int main()
{
    LL n;
    cin >> n;
    LL res;
    if(n % 2 == 0)
    {
        LL tep = n / 2;
        res = (tep % MOD) * ((n + 1) % MOD) % MOD;
    }
    else
    {
        LL tep = (n + 1) / 2;
        res = (tep % MOD) * (n % MOD) % MOD;
    }
    cout << res << endl;
    return 0;
}

 


试题F:掉发警告(规律题)

 

根据题意,我们第一年  = 1,第二年 = 2 (新掉 + 之前掉的),第三年 = 3 (新掉 + 前面掉的), 第四年 = 5 (新掉 + 之前掉的 + 第 4 - 3 年时要多掉的),第五年 = 8 (新掉 + 之前掉 + 第 5 - 3 年时要多掉的 = 1 + 5 + 2 = 8)。

所以,找到规律了,当 f(i) 表示 第 i 年的掉发,则 f(i) = 1 + f(i - 1) + f(i - 3)

 

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

LL res[60];

int main()
{
    int n;
    cin >> n;

    res[0] = 0;
    for(int i = 1;i <= n; ++i)
    {
        res[i] = res[i - 1] + 1;
        if(i - 3 >= 0) res[i] += res[i - 3];
    }

    cout << res[n] << endl;

    return 0;
}

试题G:体前屈大赛(思维)

 

一开始的想法,每次加入新的数之后,对数组进行排序,这样子就可以得到 第 k 个的值,这样子的时间复杂度是 O(q * nlogn),根据题目的数据范围,最多只能完成 40% 的数据。

但是我们发现,对于每一个数据值的范围是 -1000 到 1000,也就是说,如果我们统计每一个成绩对应的个数,那么从 -1000 到 1000 进行枚举,找到 第 k 个成绩(保证了是升序),那么时间复杂度是 O(q * 2000),不会超时。

 

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 2500;
int cnt[MAXN];

int main()
{
    int n, q;
    scanf("%d %d", &n, &q);
    int a;
    memset(cnt, 0, sizeof(cnt));
    for(int i = 0;i < n; ++i)
    {
        scanf("%d", &a);
        ++cnt[a + 1000];
    }
    int t, k;
    while(q--)
    {
        scanf("%d %d", &t, &k);
        if(t == 1)
        {
            ++cnt[k + 1000];

        }
        else
        {
            int sum = 0;
            for(int i = -1000;i <= 1000; ++i)
            {
                sum += cnt[i + 1000];  
                if(sum >= k)   // 如果发现 >= k,说明,第 k 个值是在这成绩 i 中。
                {
                    cout << i << endl;
                    break;
                }
            }
        }
    }

    return 0;

}

试题H:数列第N项(数学 + 快速幂 或者 矩阵快速幂)

 

方法一、(数学 + 快速幂)

数学推导,得到 an 的通项公式:

a_n = 3a_{n - 1} + 4 a_{n - 2}

根据高中的知识,看到这种结构,会想到构造一个新的数列,为 an 相邻之差,也就是:

a_n - k a_{n - 1} = m (a_{n - 1} - k a_{n - 2})

根据上式和原式之间的关系,我们可以求出:k=4,m=-1 \,\,or\,\, k=-1, m=4,这样子就有两种情况的构造

一种是:a_n - 4 a_{n - 1} = - (a_{n - 1} - 4 a_{n - 2}),我们将b_n = a_n - 4 a_{n - 1},因此,bn就是一个,等比数列,其中 b_2 = a_2 - 4 a_{1}, q = -1。所以

 b_n = b_2 q^{n - 2} =(a_2 - 4 a_{1})(-1)^{n - 2} = a_n - 4 a_{n - 1}

一种是 a_n + a_{n - 1} = 4 (a_{n - 1} + a_{n - 2}),我们将b_n = a_n + a_{n - 1},因此,bn就是一个,等比数列,其中 b_2 = a_2 + a_{1}, q = 4。所以

b_n = b_2 q^{n - 2} =(a_2 + a_{1})4^{n - 2} = a_n + a_{n - 1}

所以根据上面两个式子,就可以得到 :a_n = \frac{(a_2 + a_{1})4^{n - 1} + (a_2 - 4 a_{1})(-1)^{n - 2}}{5}

根据取模运算,还有除法的取模(逆元),同时要求幂,用到快速幂即可。

 

方法二、(矩阵快速幂)

我们根据 a_n = 3a_{n - 1} + 4 a_{n - 2},可以得到以下关系:

[a_3, a_2] = [a_2, a_1]\begin{bmatrix} 3 & 1\\ 4 & 0 \end{bmatrix}[a_4, a_3] = [a_3, a_2]\begin{bmatrix} 3 & 1\\ 4 & 0 \end{bmatrix} = [a_2, a_1]\begin{bmatrix} 3 & 1\\ 4 & 0 \end{bmatrix}^2,.....,[a_n, a_{n - 1}] = [a_2, a_1]\begin{bmatrix} 3 & 1\\ 4 & 0 \end{bmatrix}^{n - 2}

所以,我们要计算,矩阵的幂,假设矩阵的幂结果为 res,那么 :a_n = a_2 * res[0][0]+ a_1 * res[1][0].

因此,我们需要用到矩阵快速幂

 

方法一代码、(数学 + 快速幂)

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const LL MOD = 1e9 + 7;

LL qpow(LL a, LL b)
{
    LL ans = 1;
    while(b)
    {
        if(b & 1) ans = ans * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ans % MOD;
}

int main()
{
    LL n, a1, a2;
    cin >> n >> a1 >> a2;


    LL c1 = a2 + a1;
    LL c2 = a2 - 4 * a1;
    LL res = ((qpow(4, n-1) * c1 + qpow(-1, n - 2) * c2 + MOD) % MOD) * qpow(5, MOD - 2) % MOD;

    cout << res << endl;

    return 0;
}

 

方法二代码、(矩阵快速幂)

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const LL MOD = 1e9 + 7;

class Mat{
public:
    LL nums[2][2];

    Mat(){
        memset(nums, 0, sizeof(nums));
    }
};
int matSize = 2;

Mat& matMul(Mat& a, Mat& b)
{
    Mat res = Mat();
    for (int i = 0; i < matSize; i++) {
        for (int j = 0; j < matSize; j++) {
            for (int k = 0; k < matSize; k++) {
                res.nums[i][j] = (res.nums[i][j] + a.nums[i][k] * b.nums[k][j] % MOD) % MOD;
            }
        }
    }
    a = res;
    return a;
}

Mat qPowMat(Mat& a, LL n)  // a^n
{
    Mat ans = Mat();
    for(int i = 0;i < matSize; ++i)  // 一开始 ans 是单位矩阵
    {
        ans.nums[i][i] = 1;
    }

    while(n)
    {
        if(n & 1) ans = matMul(ans, a);  // ans *= a
        a = matMul(a, a);   // a *= a
        n >>= 1;
    }

    return ans;
}

int main()
{
    LL n, a1, a2;
    cin >> n >> a1 >> a2;
    if(n == 1)
    {
        cout << a1 << endl;
    }
    else if(n == 2)
    {
        cout << a2 << endl;
    }
    else
    {
        Mat a = Mat();
        a.nums[0][0] = 3, a.nums[0][1] = 1, a.nums[1][0] = 4;
        Mat res = qPowMat(a, n - 2);

        LL ans = a2 * res.nums[0][0] + a1 * res.nums[1][0];
        cout << ans % MOD << endl;
    }

    return 0;
}

试题I:耗时种数(最短路)

 

这道题,就是求最短路,然后判断最短路距离不同的个数(set 去重),根据数据范围,使用最短路算法SPFA即可。

 

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int INF = 0x3f3f3f3f;

int main()
{
    int n, q;
    scanf("%d %d", &n, &q);
    vector<vector<int> > dist(n, vector<int>(n, INF));
    int a, b, c;
    for(int i = 0;i < q; ++i)
    {
        scanf("%d %d %d",&a, &b, &c);
        dist[a][b] = min(dist[a][b], c);
    }
    for(int i = 0;i < n;++i) dist[i][i] = 0;

    for(int k = 0;k < n; ++k)
            for(int i = 0;i < n;++i)
                for(int j = 0;j < n;++j)
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
    
    unordered_set<int> cnt;  // 去重
    cnt.clear();
    for(int i = 0;i < n; ++i)
    {
        for(int j = 0;j < n; ++j)
        {
            if(i == j) continue;
            if(dist[i][j] == INF) continue;
            cnt.insert(dist[i][j]);
        }
    }
    int ans = cnt.size();
    
    cout << ans << endl;
    return 0;
}

试题J:传递信息(思维 + 最短路 + 大数乘法快速幂)

 

根据一开始的题目分析,我们就是求,从起点 s 到 终点 t 的一个乘积最小。但是看到题目给的每一个节点之间的距离 w 很大,那么这样子乘起来,就会很大(都无法使用 long long 存储)。

但是题目说了,每一个节点之间的距离,一定是 2 的幂,那么距离相乘,变成了 (2)幂的指数相加,这样子,每一个节点之间的距离,最大为 34(因为 2^34 大约就是 10^10)。这样子也就是 起点 s 到 终点 t 的最短路问题(最大为 34 * 5 * 10 ^ 4),可以用 int 存储。

那么将输入的距离,转化为 2 的指数,然后求最短路,这里是求一个正权,单源最短路,就用Dijkstra 算法。

得到最短路之后,我们要输出的值,应该是 2^(最短路),根据最短路可能的最大值是:34 * 5 * 10 ^ 4。同时这个数是无法用 long long 存储的,因此需要用大数(string 模拟),同时如果直接求幂,那会超时,因此要使用大数乘法快速幂(据说用FFT,但是我不会,哈哈哈哈)

 

下面是我个人的代码,只得到了 80% 的分,也就是,我们最后计算 2^(最短路) 的时候,虽然利用了string 模拟大数乘法,但是没使用快速幂,所以剩下的 20% 数据超时了。(如果要看完整AC代码,可以参考: http://oj.hzjingma.com/solution/detail?id=25378

 

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 5e4 + 50;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> PII;


vector<PII> G[MAXN];

int cal2(long long num)
{
    int res = 0;
    while(num)
    {
        ++res;
        num /= 2;
    }
    return res - 1;
}


string multi(string a, int b)  // 大数乘法,用string 模拟
{
   string res = "";
   int n = a.size();
   int c = 0;
   for(int i = 0;i < n; ++i)
   {
      int val = (a[i] - '0') * b + c;
      res += char(val % 10 + '0');
      //cout << res << endl;
      c = val / 10;
   }

   while(c)
   {
      res += char(c % 10 + '0');
      //cout << res << endl;
      c /= 10;
   }

   return res;
}


int dijkstra(int s, int t, int n)  // 表示从起点 s 到 其他点的最短路,总共 n 个点
{
    priority_queue<PII, vector<PII>, greater<PII> > pq;  
    vector<int> dist(n, INF);  // 初始化所有的 dist
    vector<int> vis(n, 0);  // 初始化所有的 vis

    dist[s] = 0;
    pq.push(make_pair(0, s));   

    while(!pq.empty()){
        int cur = pq.top().second;  
        pq.pop();

        if(vis[cur] == 1) continue;
        vis[cur] = 1;
        for(int i = 0;i < G[cur].size();++i)  
        {
            int e = G[cur][i].first, w = G[cur][i].second;  
            if(dist[e] > dist[cur] + w)
            {
                dist[e] = dist[cur] + w;
                pq.push(make_pair(dist[e], e));
            }
        }
    }
    // cout << dist[t] << endl;
    if(dist[t] == INF) return -1;
    else return dist[t];

}


int main()
{
    int n, m, s, t;
    scanf("%d %d %d %d", &n, &m, &s, &t);

    for(int i = 0;i < n; ++i) G[i].clear();
    for(int i = 0;i < m;++i)
    {
        int u, v;
        long long w;
        scanf("%d %d %lld", &u, &v, &w);
        // cout << u << " " << v << " " << cal2(w) << endl;
        G[u - 1].push_back(make_pair(v - 1, cal2(w)));  // 构造图的时候,有向图,同时距离是 2 的指数
        // cout << u << " " << v << " " << cal2(w) << endl;
    }

    // 最短路,正权
    int ans = dijkstra(s - 1, t - 1, n);  // 求 两点之间的最短路

    if(ans == -1) cout << -1 << endl;
    else
    {
        string res = "1";
        for(int i = 0;i < ans; ++i)  // 模拟求 2 ^ ans,数据过大,用的是string模拟乘法,但是如果数据更大,那就会超时,所有进一步优化,是要利用FFT求大数乘法。
        {
            res = multi(res, 2);
        }
        reverse(res.begin(), res.end());
        cout << res << endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
时间复杂度(渐近时间复杂度的严格定义,NP问题,时间复杂度的分析方法,主定理)   排序算法(平方排序算法的应用,Shell排序,快速排序,归并排序,时间复杂度下界,三种线性时间排  序,外部排序)   数论(整除,集合论,关系,素数,进位制,辗转相除,扩展的辗转相除,同余运算,解线性同余方程,中国剩余定理) 指针(链表,搜索判重,邻接表,开散列,二叉树的表示,多叉树的表示) 按位运算(and,or,xor,shl,shr,一些应用) 图论(图论模型的建立,平面图,欧拉公式与五色定理,求强连通分量,求割点和桥,欧拉回路,AOV问题,AOE问题,最小生成树的三种算法最短路的三种算法,标号法,差分约束系统,验证二分图,Konig定理,匈牙利算法,KM算法,稳定婚姻系统,最大流算法,最小割最大流定理,最小费用最大流算法) 计算几何(平面解几及其应用,向量,点积及其应用,叉积及其应用,半平面相交,求点集的凸包,最近点对问题,凸多边形的交,离散化与扫描) 数据结构(广度优先搜索,验证括号匹配,表达式计算,递归的编译,Hash表,分段Hash,并查集,Tarjan算法,二叉堆,左偏树,二斜堆,二项堆,二叉查找树,红黑树,AVL平衡树,Treap,Splay,静态二叉查找树,2-d树,线段树,二维线段树,矩形树,Trie树,块状链表) 组合数学(排列与组合,鸽笼原理,容斥原理,递推,Fibonacci数列,Catalan数列,Stirling数,差分序列,生成函数,置换,Polya原理) 概率论(简单概率,条件概率,Bayes定理,期望值) 矩阵(矩阵的概念和运算,二分求解线性递推方程,多米诺骨牌棋盘覆盖方案数,高斯消元) 字符串处理(KMP,后缀树,有限状态自动机,Huffman编码,简单密码学) 动态规划(单调队列,凸完全单调性,树型动规,多叉转二叉,状态压缩类动规,四边形不等式) 博奕论(Nim取子游戏,博弈树,Shannon开关游戏) 搜索(A*,ID,IDA*,随机调整,遗传算法

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值