ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

2021牛客寒假算法基础集训营6 解题补题报告

2021-02-25 17:01:56  阅读:276  来源: 互联网

标签:const 括号 int text scanf long 牛客 补题 集训营


官方题解

A题 回文括号序列计数 (思维)

这题耍了一个小障眼法:其实 \(()\) 并不是一个回文串(虽然看起来像)

那么我们根据括号串的定义:

  1. 空串是一个括号串
  2. 若 \(s,t\) 是括号串,那么 \(s+t\) 也是一个括号串
  3. 若 \(s\) 是括号串,那么 \('('+s+')'\) 也是一个括号串

显然,任何一个括号串经过操作 \(3\) 之后不论怎么变换,不可能变为一个回文串(开头必然是 \('('\) ,结尾必然是 \(')'\)),但是所有长度大于 \(0\) 的括号串都至少需要经过一次操作 \(3\) 才能形成

那么答案就显而易见了:\(n=0\) 时存在一个空串,\(n \geq 1\) 时不存在回文括号串

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        int n;
        scanf("%d", &n);
        puts(n ? "0" : "1");
    }
    return 0;
}

B题 系数

官方给了两个思路:

  1. 答案对 \(3\) 取模,所以我们注意到在此情况下,\((x^2+x+1)^n=(x^2-2x+1)^n=(x-1)^{2n}\) (对括号里面减去 \(3x\),答案对 \(3\) 取模后不变),然后就是显然的高中二项式定理知识了
  2. 打表找规律

最后写出来这个组合数要用 \(\text{Lucas}\) 定理来求,可以看我之前写的这个博客:链接

C题 末三位 (签到)

签到题,找规律也行,快速幂也行,还有想要欧拉降幂的,理论上都能过

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

int main()
{
    int n;
    while (scanf("%d", &n) != EOF) {
        if (n == 0) puts("001");
        else if (n == 1) puts("005");
        else if (n == 2) puts("025");
        else {
            if (n % 2 == 1) puts("125");
            else puts("625");
        }
    }
    return 0;
}

D题 划数 (数学,分类讨论)

显然的,根据取模的数学性质,不管怎么选,最后得到的值必然是 \((\sum\limits_{i=1}^{n}a_i)\mod 11\) 。

假设答案是 \(x\) ,那么显然 \((x+cnt) \mod 11 = (\sum\limits_{i=1}^{n}a_i)\mod 11\) ,而且 \(x\) 的值因为被取模过,所以范围只会在 \([0,11)\) 中,不管是数学求解还是直接枚举,都是 \(O(1)\) 的复杂度,总复杂度为 \(O(n)\) 。

然后我交了,然后就...... \(WA\) 了(逃

后来看着数据规模才发现,当 \(n=2\) 的时候,\(x\) 并没有被取模过,所以此时上面这个式子并不适用,需要特判

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 150010;
int n, x, a[N];
int main()
{
    while (scanf("%d%d", &n, &x) != EOF) {
        int sum = 0;
        for (int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        if (n == 2) {
            if (x == a[1]) printf("%d\n", a[2]);
            else printf("%d\n", a[1]);
            continue;
        }
        for (int i = 1; i <= n; ++i)
            sum = (sum + a[i]) % 11;
        for (int i = 0; i < 11; ++i)
            if (sum == (i + x) % 11) {
                printf("%d\n", i);
                break;
            }
    }
    return 0;
}

E题 网格

DP,感兴趣可以看官方题解

F题 组合数问题 (数学:二项式定理+复数快速幂)

高中我们学过 \(C_n^0+C_n^2+......+C_n^n=2^{n-1}\),因为

\(S1=(1+1)^n=C_n^0+C_n^1+C_n^2+......+C_n^{n-1}+C_n^n\)

\(S2=(1-1)^n=C_n^0+C_n^1+C_n^2+......-C_n^{n-1}+C_n^n\)

便得到 \(ans=\frac{S1+S2}{2}=2^{n-1}\)

这题就离谱了,我们并不是构造的很出来,所以我们直接给出答案:

\((1+1)^n-(1-1)^n=2(C_n^0+C_n^2+......+C_n^n)\)

\((1+i)^n-(1-i)^n=2(C_n^0-C_n^2+......+C_n^n)\) (基于复数的二项式定理,没想到吧?反正我想不到(逃 ))

所以 \(ans=\frac{2^n+(1+i)^n+(1-i)^n}{4}\),我们写一个复数快速幂就好了(逃

G题 机器人 (状压DP/贪心)

说实话,出题人给的正解是状压 \(DP\) 就离谱,难怪这题给的数据规模这么奇怪(逃

想看状压写法的可以去看官方题解,这里我给出一个基于贪心的解法,使用临项交换法证明(想要系统学习的可以来看我以前写的一个博客:临项交换法在贪心算法中的应用):

假定我们已经按照某种顺序将这些机器人排序好了,我们尝试挑选两个相邻机器人 \(i\) 和 \(j\) ,看看 \(x\) 在经历两者变换之后会咋样:

  1. 先 \(i\) 后 \(j\) :最后值为 \(a_j(a_ix+b_i)+b_j=a_ia_jx+a_jb_i+b_j\)
  2. 先 \(j\) 后 \(i\) :最后值为 \(a_i(a_jx+b_j)+b_i=a_ia_jx+a_ib_j+b_i\)

显然,当 \(a_jb_i+b_j>a_ib_j+b_i\) 时,应当先 \(i\) 后 \(j\) ,否则应该先 \(j\) 后 \(i\) 。显然这样不会使得结果更差。

推广到 \(n\) 长度的数列,那么就是对于每个机器人,按照上面这个式子进行排序(或者说按照 \(\frac{b}{a-1}\) 从大到小排序,但是排序函数还是按照上面那个写,不然容易出现浮点误差,除零这些乱七八糟的问题),这样的顺序可以保证最后的答案就是最优解。

然后,我欢欢喜喜的写好代码,交上去,然后...... \(WA\) 了(逃

后来发现,出题人一定是借鉴了 \(\text{NOIP2012}\) 国王游戏的出题人的精神:这题没有让我们取模啥的,但是最大数据规模大约在 \(20^{20}\) 左右,超过了 \(\text{long long}\) 的表示范围,所以我们必须得写高精度

如果不想手写高精度,那么只有两种方法:

  1. 用下 \(\text{int128}\),可以简单应付下不是太离谱的数据(例如本题)(这个好像只在基于 \(\text{Linux}\) 的评测机上面才能用,\(\text{Windows}\) 好像用不了
  2. 上 \(\text{Java}\) 或者 \(\text{python}\) 这种自带高精度的语言(但是多了 \(\text{TLE}\) 的风险
#include<bits/stdc++.h>
using namespace std;
 
void print(__int128 x)
{
    if (!x) return;
    if (x < 0) putchar('-'), x = -x;
    print(x / 10);
    putchar(x % 10 + '0');
}
 
struct Robot {
    int a, b;
    bool operator < (const Robot &rhs) const {
        return rhs.a * b + rhs.b > a * rhs.b + b;
    }
};
const int N = 30;
int n;
Robot r[N];
int main()
{
    int x;
    scanf("%d%d", &n, &x);
    for (int i = 1; i <= n; ++i)
        scanf("%d%d", &r[i].a, &r[i].b);
    sort(r + 1, r + n + 1);
    __int128 ans = x;
    for (int i = 1; i <= n; ++i) {
        int a = r[i].a, b = r[i].b;
        ans = a * ans + b;
    }
    print(ans);
    return 0;
}

H题 动态最小生成树

这题我暴力都不会写,告辞

I题 贪吃蛇 (BFS)

看起来有点小懵圈,但是简单理解下题意后(外加看了下飞速增加的榜单),发现这玩意就是一个迷宫 \(BFS\) 的题,直接写就好了,复杂度 \(O(nm)\) 。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 110;
int a[N][N], vis[N][N];
int n, m, sx, sy, tx, ty;
inline bool can(int x, int y) { return 1 <= x && x <= n && 1 <= y && y <= m && a[x][y]; }
//
struct Node {
    int x, y, dis;
};
queue<Node> q;
int main()
{
    scanf("%d%d", &n, &m);
    scanf("%d%d%d%d", &sx, &sy, &tx, &ty);
    for (int i = 1; i <= n; ++i) {
        char s[N];
        scanf("%s", s + 1);
        for (int j = 1; j <= m; ++j)
            if (s[j] == '.') a[i][j] = 1;
            else a[i][j] = 0;
    }
    vis[sx][sy] = 1, q.push((Node){sx, sy, 0});
    while (!q.empty()) {
        Node now = q.front(); q.pop();
        int x = now.x, y = now.y, dis = now.dis;
        if (x == tx && y == ty) {
            printf("%d", dis * 100);
            return 0;
        }
        //
        if (can(x - 1, y) && !vis[x - 1][y])
            vis[x - 1][y] = 1, q.push((Node){x - 1, y, dis + 1});
        if (can(x + 1, y) && !vis[x + 1][y])
            vis[x + 1][y] = 1, q.push((Node){x + 1, y, dis + 1});
        if (can(x, y - 1) && !vis[x][y - 1])
            vis[x][y - 1] = 1, q.push((Node){x, y - 1, dis + 1});
        if (can(x, y + 1) && !vis[x][y + 1])
            vis[x][y + 1] = 1, q.push((Node){x, y + 1, dis + 1});
    }
    printf("-1");
    return 0;
}

J题 天空之城 (字符串处理,最小生成树)

读完题目之后,简单理解下,就可以理解题目的意思:

给定一个 \(n\) 点 \(m\) 边的带权无向边图,尝试选出部分边,使得基于这些边构建的图可以使得 \(n\) 个点全部联通,并且使得边权和最小。

我已经失了智了,想了好长时间才发现,这玩意不就是让我们求最小生成树吗?

对于将城市名映射为 \(1\) 到 \(n\) 的数字的处理比较烦,我们可以 \(Hash\) ,也可以直接用 \(map\) 啥的来写。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 5010, M = 200010;
struct Edge {
    int u, v, d;
    bool operator < (const Edge &rhs) const {
        return d < rhs.d;
    }
}edges[M];
//
int fa[N];
void init(int n) {
    for (int i = 1; i <= n; ++i) fa[i] = i;
}
int find(int x) {
    if (x != fa[x]) fa[x] = find(fa[x]);
    return fa[x];
}
void merge(int x, int y) {
    x = find(x), y = find(y);
    if (x != y) fa[x] = y;
}
//
int n, m, tot;
map<string, int> Hash;
int main()
{
    while (scanf("%d%d", &n, &m) != EOF) {
        tot = 0;
        Hash.clear();
        char a[20], b[20];
        scanf("%s", a);
        for (int i = 1; i <= m; ++i) {
            int u, v, dis;
            scanf("%s%s%d", a, b, &dis);
            u = Hash[a], v = Hash[b];
            if (u == 0) u = Hash[a] = ++tot;
            if (v == 0) v = Hash[b] = ++tot;
            //printf("u=%d v=%d d=%d\n", u, v, dis);
            edges[i].u = u, edges[i].v = v, edges[i].d = dis;
        }
        init(n);
        sort(edges + 1, edges + m + 1);
        int cnt = 0;
        LL ans = 0;
        for (int i = 1; i <= m; ++i) {
            int u = edges[i].u, v = edges[i].v;
            if (find(u) != find(v)) {
                merge(u, v);
                ++cnt, ans += edges[i].d;
            }
            if (cnt == n - 1) break;
        }
        if (cnt == n - 1) printf("%lld\n", ans);
        else puts("No!");
    }
    return 0;
}

标签:const,括号,int,text,scanf,long,牛客,补题,集训营
来源: https://www.cnblogs.com/cyhforlight/p/14447929.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有