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

官方题解 视频讲解

A题 (DP/组合数学)

法一:动态规划

\(dp(i)\) 为长度为 \(i\) 且包含 \(us\) 的字符串的总数量,接下来我们考虑递推关系:

  1. 如果前 \(i-1\) 项中已经含有 \(us\),那么第 \(i\) 项什么都行,总计 \(26*dp(i-1)\)

  2. 如果前 \(i-1\) 项里面只有 \(u\),那么第 \(i\) 项必须为 \(s\)。我们考虑下前 \(i-1\) 项里面只有 \(u\) 没有 \(s\) 的情况:

    总计可以构成 \(26^{i-1}\) 种字符串,其中完全没有 \(u\) 的有 \(25^{i-1}\) 种,有 \(u\) 又有 \(s\) 的一共 \(dp(i-1)\) 种,所以总计 \(26^{i-1} - 25^{i-1} - dp(i-1)\) 种。

综上,我们可以得到 \(DP\) 方程:\(dp(i) = 25 *dp(i-1) + 26^{i-1}-25^{i-1}\),最后的答案就是 \(\displaystyle\sum\limits_{i=1}^{n}dp(i)\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1000010;
const LL mod = 1e9 + 7;
int n;
LL dp[N], pow_25[N], pow_26[N];
int main()
{
    cin>>n;
    pow_25[0] = pow_26[0] = 1;
    for (int i = 1; i <= n; ++i)
        pow_25[i] = 25 * pow_25[i-1] % mod,
        pow_26[i] = 26 * pow_26[i-1] % mod;
    dp[1] = 0, dp[2] = 1;
    for (int i = 3; i <= n; ++i)
        //记得这里要在括号里面加一个mod,不然可能要减出问题来
        dp[i] = (25 * dp[i-1] + pow_26[i-1] - pow_25[i-1] + mod) % mod;
    LL ans = 0;
    for (int i = 1; i <= n; ++i)
        ans = (ans + dp[i]) % mod;
    printf("%lld", ans);
    return 0;
}

法二:组合数学

理论上,这个公式是可以推出来的(也是我比赛时的思路),但是这玩意估计太难,到现在还没看到有啥正确的组合公式

B题 括号 (构造)

如果不限制的话,我们显然可以找出一种构造方式:一开始给一个左括号,然后跟着一堆右括号,或者反过来(例如 \(())))))))))\) 或者 \(((((((((((((((()\)),奈何题目对输出有限制,没办法

虽然有限制,没法直接用上面这方法,但是我们如果推广一下:\((()))))))))))\),如果一开始给的是两个呢?三个呢?

这种方法只能对应某个数的倍数的情况,但是我们再换一个思路呢?如果在这后面再加上类似的东西呢?例如这样:\((()))())\)

显然,每增加一个右括号,括号串中增加的合法括号对的数量为该右括号左边的左括号数量之和。

根据上面的铺垫,我们不难找出一种构造方式,将 \(k\) 表示成 \(ax+by\) 的形式,先给 \(a\) 个左括号,然后给 \(x\) 个右括号,然后 \(b\) 个左括号,再跟着 \(y\) 个右括号,最后能够构成 \(k\) 个括号对,并且总长度为 \(a+b+x+y\)

字符串最大长度为 \(100000\),理论上能构造出来的最多的括号对数量为 \(49999^2\),大于 \(k\) 的最大值,所以这种构造是正确的。

另外,\(k=0\) 的时候记得特判下,有可能出 \(bug\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL k;
void output(LL a, LL b) {
    LL low = k / b;
    LL cnt = k - low * b;
    for (LL i = 1; i <= low; ++i)
        putchar('(');
    for (LL i = 1; i <= b - cnt; ++i)
        putchar(')');
    if (cnt) {
        putchar('(');
    for (LL i = 1; i <= cnt; ++i)
        putchar(')');
    }
}
int main()
{
    cin>>k;
    for (LL b = 1; ; ++b) {
        LL a_max = 100000 - b;
        if (k <= b * a_max) {
            output(a_max, b);
            return 0;
        }
    }
    return 0;
}

C题 红和蓝

D题 点一成零

E题 三棱锥之刻 (计算几何)

听说是一个纯 \(J8\) 算的数学题,我就不管了,直接给一个许佬的博客链接:链接

F题 (签到/贪心)

最低显然为 \(0\) (从头错到位),最高的话要看每一题:两人相同则加 \(2\)(全对),不同则加 \(1\) (肯定错一个)。

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n, a[N], b[N];
int main()
{
    cin>>n;
    for (int i = 1; i <= n; ++i) {
        char ch;
        cin>>ch;
        a[i] = ch - 'A' + 1;
    }
    for (int i = 1; i <= n; ++i) {
        char ch;
        cin>>ch;
        b[i] = ch - 'A' + 1;
    }
    int max_score = 0;
    for (int i = 1; i <= n; ++i) {
        if (a[i] == b[i]) max_score += 2;
        else {
            max_score += 1;
        }
    }
    cout<<max_score<<' '<<0;
    return 0;
}

G题 好玩的数字游戏 (大模拟)

告辞

H题 幂塔个位数的计算 (数学:找规律/欧拉降幂)

找规律这种东西全凭灵性,没啥好总结的,想看的自己去看官方题解

我整个不一样的吧:欧拉降幂

(感谢 dalao小粉兔的题解)简单来说就是:欧拉定理

有了这玩意,我们便可以将这个 \(nt\) 数学式子化简,直至变成一个我们可以肉眼推出的形式。

简单来说,分成以下几类(过程略,心累):

  • \(a\%10=0/1/5/6\),对应输出
  • \(n=1\),直接输出 \(a\%10\)
  • \(a=2,n=2\),输出 \(4\)
  • \(a=3,n=2\),输出 \(7\) (这组和上面那组都是对付第一次欧拉降幂的一组特例)
  • \(a \geq 4,n=2\),输出 \((a\%10)^{a\%4+4}\%10\) (这组是对付第二次欧拉降幂的一组特例)
  • 其余情况,输出 \((a\%10)^{(a\%4)^{a\%2+2}\%4+4}\%10\) (通解)
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
char str_a[N], str_n[N];
int read(char *s, int mod) {
    int ans = 0;
    int i = 0;
    for (int i = 0; s[i] != '\0'; ++i)
        ans = (ans * 10 + s[i] - '0') % mod;
    return ans;
}
int quickpow(int a, int b, int mod) {
    if (b == 0) return 1;
    if (b == 1) return a;
    int half = quickpow(a, b / 2, mod);
    if (b % 2 == 0) return half * half % mod;
    return half * half % mod * a % mod;
}
int main()
{
    scanf("%s", str_a + 1);
    scanf("%s", str_n + 1);
    //
    int L1 = strlen(str_a + 1);
    int L2 = strlen(str_n + 1);
    int a_10 = read(str_a + 1, 10);
    int a_2  = read(str_a + 1, 2);
    int a_4  = read(str_a + 1, 4);
    if (a_10 == 0 || a_10 == 1 || a_10 == 5 || a_10 == 6)
        printf("%d", a_10);
    else if (L2 == 1 && str_n[1] == '1')
        printf("%d", a_10);
    else if (L2 == 1 && str_n[1] == '2') {
        if (L1 == 1 && str_a[1] == '2') printf("4");
        if (L1 == 1 && str_a[1] == '3') printf("7");
        else printf("%d", quickpow(a_10, a_4 + 4, 10));
    }
    else printf("%d", quickpow(a_10, quickpow(a_4, a_2 + 2, 4) + 4, 10));
    return 0;
}

I题 限制不互素对的排列 (构造)

\(k \leq \frac{n}{2}\) 真的关键,不然这题没法写了都(虽然有这个条件的题目我都没写出来)

有一种(并非那么)显然的排列方式:所有偶数排在一起,所有奇数排在一起,成功构成的对的数量就在 \(k\) 附近。如果缺,可以让奇偶数的连接点是 \(6\)\(3\);多的话,把左边偶数排列拆掉一些就好了。

另外,对于 \(n \leq 5\) 的情况(这时候没有 \(6\) 可以拿来和 \(3\) 拼接),直接特判打表吧。

思路是这样的,但是不代表代码很好写(逃)

#include<bits/stdc++.h>
using namespace std;
int n, k;
int main()
{
    cin >> n >> k;
    //special
    if (k == 0) {
        for (int i = 1; i <= n; ++i)
            printf("%d ", i);
        return 0;
    }
    if (n <= 3) printf("-1");
    else if (n == 4) {
        if (k == 1) printf("2 4 1 3");
        else if (k == 2) printf("-1");
    }
    else if (n == 5) {
        if (k == 1) printf("2 4 1 3 5");
        else if (k == 2) printf("-1");
    }
    if (n < 6) return 0;
    //solve
    if (2 * k == n || 2 * k + 1 == n) {
        for (int i = 1; i <= n; ++i)
            if (i % 2 == 0 && i != 6) printf("%d ", i);
        printf("6 3 ");
        for (int i = 1; i <= n; ++i)
            if (i % 2 == 1 && i != 3) printf("%d ", i);
        return 0;
    }
    bool vis[100010];
    memset(vis, 0 , sizeof(vis));
    for (int i = 1, cnt = 1; 2 * i <= n && cnt <= k + 1; ++i, ++cnt) {
        printf("%d ", 2 * i);
        vis[2 * i] = 1;
    }
    for (int i = 1; i <= n; ++i)
        if (!vis[i]) printf("%d ", i);

    return 0;
}

J题 一群小青蛙呱蹦呱蹦呱 (数论)

这个筛子,筛掉的是所有只有一种质因子的数,剩下来的数都有一种以上的质因子。

求他们的 \(lcm\),我们需要借助算数基本定理来计算:

对于其中任意剩下来的数,都可以表示成这样:\(p_1^{a_1}p_2^{a_2}p_3^{a_3}......p_n^{a_n}\) 的形式(\(p_i\) 是质数)

对应的,他们的 \(lcm\) 就是 \(p_1^{\max{a_1}}p_2^{\max{a_2}}p_3^{\max{a_3}}......p_n^{\max{a_n}}\)

显然,\(p1=2\)\(\max a_1\) 的值,相当于使得\(3*2^k \leq n\) 的情况下的 \(k\) 的最大值,即 \(\max a_1 = \lfloor log_2^{\frac{n}{3}} \rfloor\),对 \(lcm\) 的贡献就是 \(2^{\max a_1}\)

对于 \(p_i(i \geq 2)\)\(\max a_i= \lfloor log_{p_i}^{\frac{n}{2}} \rfloor\),对 \(lcm\) 的贡献就是 \(p_i^{\max a_i}\)

那么很显然,我们的任务就是筛出不大于 \(n\) 的所有质数,然后一一累计贡献就好了。(看看这 \(n\) 的规模,线性筛我都觉得过不了)

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

#define LL long long
const int N = 80000010;
const LL mod = 1e9 + 7;

int n, num[N], prim[5000060];
int pn = 0;

void table()
{
    memset(num, -1, sizeof(num));
    for (int i = 2; i < N; i++) {
        if (num[i]) prim[pn++] = i;
        for (int j = 0; j < pn && 1LL * i * prim[j] < N; j++) {
            num[i * prim[j]] = 0;
            if (i % prim[j] == 0) break;
        }
    }
}
int main()
{
    table();
    cin >> n;
    if (n < 6) {
        cout << "empty";
        return 0;
    }
    LL res = 1;
    for (int i = 1; prim[i] <= n / 2; i++) {
        LL p = prim[i], temp = 1;
        while (temp * p <= n / 2)
            temp *= p;
        res = res * temp % mod;
    }
    LL temp2 = 1;
    while (temp2 * 2 <= n / 3)
        temp2 *= 2;
    res = res * temp2 % mod;
    cout << res;
    return 0;
}
posted @ 2021-02-03 09:53  cyhforlight  阅读(99)  评论(0编辑  收藏  举报