2022牛客寒假算法基础集训营1解题记录

文章目录

  • 前言
  • Problem A 九小时九个人九扇门
    • 题意
    • 方法解析
    • 代码
  • Problem C Baby's first attempt on CPU
    • 题意
    • 方法解析
    • 代码
  • Problem D 牛牛做数论
    • 题意
    • 方法解析
    • 代码
  • Problem E 炸鸡块君的高中回忆
    • 题意
    • 代码
  • Problem F 中位数切分
    • 题意
    • 方法解析
    • 代码
  • Problem H 牛牛看云
    • 题意
    • 方法解析
    • 代码(solution2)
  • Problem I B站与各唱各的
    • 题意
    • 方法解析
    • 代码
  • Problem J 小朋友做游戏
    • 题意
    • 代码
  • Problem L 牛牛学走路
    • 题意
    • 代码

前言

2020牛客寒假算法基础集训营第一场比赛的解题记录。原博客链接:http://www.retrogogogo.top/2022/01/26/algorithm/nowcoder/winter2022_1/

2022牛客寒假算法基础集训营1解题记录_第1张图片

Problem A 九小时九个人九扇门

链接:https://ac.nowcoder.com/acm/contest/23106/A
来源:牛客网

题意

   9位主人公被困在一座大型的豪华巨轮中,每个人手上都有一个奇怪的手表,手表上有一个数字,$ 9 $ 个人的数字分别是 1 − 9 1−9 19;在巨轮中,还有很多紧闭的数字门,每扇数字门上也有一个 $1−9 $的数字,要想打开数字门逃出生天,主角们必须要满足一个奇怪的条件: k k k 个人能够打开门上数字为 d d d 的一扇数字门,当且仅当这 k k k 个人的腕表数字之和的数字根恰好为 d d d。 一个数字的数字根是指:将该数字各数位上的数字相加得到一个新的数,直到得到的数字小于 10 10 10

   输入 n n n n n n 个数 { a n {a_n} an} ,求可以打开 1 − 9 1-9 19 号门的人物组合有多少种。

方法解析

   首先我们需要知道一个结论:一个数的数字根等于这个数对9取模的结果(特殊的情况是取模结果为0时数字根为9)。那我们要求的就是 { a n {a_n} an} 中选择一些数字组成 1 − 9 1-9 19 的方案数。

   该问题可以转化为一个01背包问题,dp即可。定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 数组 ,表示从前个数中选择一些数字,使得数字和的数字根为j的方案数(即求和对9取模得的方案数)。 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − a [ i ] ] + d p [ i − 1 ] [ j + 9 − a [ i ] ] dp[i][j] = dp[i - 1][j]+dp[i - 1][j - a[i]]+dp[i - 1][j + 9 - a[i]] dp[i][j]=dp[i1][j]+dp[i1][ja[i]]+dp[i1][j+9a[i]],当然,这涉及到下标是否合法问题。

代码

/*
 * @Author: Retr0.Wu 
 * @Date: 2022-01-25 00:23:59 
 * @Last Modified by: Retr0.Wu
 * @Last Modified time: 2022-02-02 02:46:04
 */
#include 
using namespace std;
const int mod = 998244353;

typedef long long ll;
ll dp[100010][10]; // 从前个数中选择一些数字,使得数字和的数字根为j的方案数(即求和对9取模得的方案数)
void solveA()
{
    int n;
    cin >> n;
    vector a(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        a[i] %= 9;       // 一个数的数字根等于这个数对9取模的结果(取模得0则表示数字根为9,这里先不做处理,最后输出时做处理即可)
        dp[i][a[i]] = 1; // 只用a[i]可以使dp[i][a[i]]至少有1种方案
    }
    //dp[1][a[1]] = 1;
    for (int i = 2; i <= n; i++)
    {
        for (int j = 0; j <= 8; j++)
        {
            // 至少有dp[i][j]+dp[i-1][j]种(只用a[i]和用a[1...i-1])
            ll tmp = (dp[i - 1][j] + dp[i][j]);
            // 无非有俩种情况使得原数字根加上a[i]后的数字根为j
            if (j - a[i] >= 0)
            {
                tmp += (dp[i - 1][j - a[i]]);
            }
            if (j + 9 - a[i] >= 0 && j + 9 - a[i] <= 8)
            {
                tmp += (dp[i - 1][j + 9 - a[i]]);
            }
            dp[i][j] = tmp;
            dp[i][j] %= mod; // 特别注意:需要此时才能mod,防止大值被模后反而小于小值从而影响前面的max结果
        }
    }
    for (int i = 1; i <= 8; i++)
    {
        cout << dp[n][i] << " ";
    }
    cout << dp[n][0] << endl;
}

Problem C Baby’s first attempt on CPU

链接:https://ac.nowcoder.com/acm/contest/23106/C
来源:牛客网

题意

   在CPU流水线式的架构中,有一个先写后读相关问题:一条语句A写入寄存器的数据若想被语句B读到,则语句B和A之间至少要间隔三条语句,如果不够三条就做插入空语句处理。

   输入第一行包括一个整数 n ( 3 ≤ n ≤ 100 ) n(3≤n≤100) n(3n100) 程序原有语句总数。接下来有 n n n 行,第iii行描述了第 i i i 条程序语句,每行有三个数字。第 i i i 行第 j j j 个数字 a i , j ∈ 0 , 1 a_{i,j}∈{0,1} ai,j0,1 表示第 i i i 句与第 i − j i-j ij 句间是否发生了先写后读相关,为 1 1 1 表示有发生先写后读相关(即第 i − j i-j ij 句写入了某一寄存器,而第 i i i 句又要读取同一寄存器),为 0 0 0 表示没有。

   求为了完全消除先写后读相关至少需加入多少条空语句。

方法解析

   有点贪心的感觉,每个语句尽量直接接上一个语句,如果出现先写后读相关问题,就差多少补多少空语句,循环下一个语句,实现时简单模拟即可,我们开一个 p o s [ i ] pos[i] pos[i] 数组去表示第 i i i 个语句所在的位置,取初始值 p o s [ i ] = p o s [ i − 1 ] + 1 pos[i]=pos[i - 1]+1 pos[i]=pos[i1]+1

   然后判断:若第 $ i$ 个语句比放寄存器的语句 + 4 +4 +4 还要大或者相等,说明不会有先写后读相关问题,保持原值。 否则就要插入空语句。至少到 i − j i-j ij 语句后面第 4 4 4 个位置,取值 pos[i-j]+4。最后得到最终的总语句数,减去非空语句就是答案了。

代码

void solveC()
{
    int n;
    cin >> n;
    int a[110][5] = {0};
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= 3; j++)
            cin >> a[i][j];
    }
    int cnt = 0;
    vector pos(110);
    pos[1] = 1;
    for (int i = 2; i <= n; i++)
    {
        pos[i] = pos[i - 1] + 1; // 第i个命令所在的位置,取初始贪心值pos[i - 1]+1
        for (int j = 1; j <= 3; j++)
        {
            if (a[i][j] == 1) // i-j 放 , i 取
            {
                // 贪心
                // 若第i个命令比放寄存器的命令+4还要大或者相等,说明不会有先写后读相关问题,保持原贪心值;
                // 否则就要插入空命令至少到i-j命令后面第4个位置,取贪心值 pos[i-j]+4。
                pos[i] = max(pos[i], pos[i - j] + 4);
            }
        }
    }
    cout << pos[n] - n << endl; // 本来有n个命令,现在最后一个命令至pos[n]位,既有pos[n]-n个空命令
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0ApW6Y5-1644255504122)(winter2022_1/封面.png)]

Problem D 牛牛做数论

链接:https://ac.nowcoder.com/acm/contest/23106/D
来源:牛客网

题意

2022牛客寒假算法基础集训营1解题记录_第2张图片

方法解析

   这道题需要我们对素数的性质够敏感,直接说结论吧:

   问题一:根据自然顺序取前 k k k 个素数的累乘,答案就是最大的且不超过n的那一个。

问题二:问题范围内最大的素数。(从大到小暴力判读是否为素数找到第一个即可,因为1e9内每一个素数间间隔最大也不会超过300,不会爆时间)

代码


bool isprime(int x)
{
    for (int i = 2; i * i <= x; i++)
    {

        if (x % i == 0)
        {
            return false;
        }
    }
    return true;
}

void solveD()
{
    vector primes;
    ll tp = 1;
    for (int i = 2; i <= 2000; i++)
    {
        if (i * tp <= 1000000000 && isprime(i))
        {
            tp *= i; // i: 2 3 5 7 11 13 17 19 23
            primes.push_back(tp);
        }
    }
    int T;
    cin >> T;
    while (T--)
    {
        int n;
        cin >> n;
        if (n == 1)
        {
            cout << -1 << endl;
            continue;
        }

        for (int i = 0; i < primes.size(); i++)
        {
            if (primes[i] > n)
            {
                cout << primes[i - 1] << " ";
                break;
            }
        }
        if (primes[primes.size() - 1] <= n)
            cout << primes[primes.size() - 1] << " ";

        for (int i = n; i >= 2; i--)
        {
            if (isprime(i))
            {
                cout << i << endl;
                break;
            }
        }
    }
}

Problem E 炸鸡块君的高中回忆

链接:https://ac.nowcoder.com/acm/contest/23106/E
来源:牛客网

题意

  签到题。

代码

void solveE()
{

    int T;
    cin>>T;
    while(T--){
        int n,m;
        cin>>n>>m;
        if(n==1){
            cout<<1<

Problem F 中位数切分

链接:https://ac.nowcoder.com/acm/contest/23106/F
来源:牛客网

题意

  给定一个长为 n n n 的数组 { a a a} 和一个整数 m m m,你需要将其切成连续的若干段,使得每一段的中位数都大于等于 m m m ,求最多可以划分成多少段。(偶数个数的中位数为中间两个数中较小的那一个)

方法解析

  这题如果不去严谨地证明,就是一道思维题,我们可以这么贪心地去思考:对于每一段,我们都取大于等于m的数量比小于m的多一个即可。这样的话答案就是大于等于m的数量减去小于m的数量。

代码

void solveF()
{
    ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while (T--)
    {
        int n, m;
        cin >> n >> m;
        int cnt1 = 0, cnt2 = 0;
        vector a(n + 1);
        for (int i = 0; i < n; i++)
        {
            cin >> a[i];
            if (a[i] < m)
                cnt1++;
            else
                cnt2++;
        }
        if (cnt1 >= cnt2)
            cout << -1 << endl; // 注意相等时由于中位数偏小依旧不可行
        else
            cout << cnt2 - cnt1 << endl;
    }
}

Problem H 牛牛看云

链接:https://ac.nowcoder.com/acm/contest/23106/H
来源:牛客网

题意

   定义式子 Σ i = 1 n Σ j = i n ∣ a i + a j − 1000 ∣ Σ_{i=1}^nΣ_{j=i}^{n}∣ai+aj−1000∣ Σi=1nΣj=inai+aj1000 ,给出整数序列 { a a a} : a i ( 0 ≤ a i ≤ 1000 ) a_i(0≤a_i≤1000) ai(0ai1000)求这个式子的值。

方法解析

   方法一:这个数值范围很小,可以保存 c n t [ ] cnt[] cnt[i] 表示 i 出现的次数,枚举(,)对。

   方法二:当然这个方法仅限这种数值范围小的情况。我们也可以利用绝对值的性质对其求解:我们先保存一个后缀数组 l a s t [ N ] last[N] last[N],然后对整数序列 { a a a} 进行 $i、j $ 的嵌套循环,对于每一个 i i i , 我们都要求 Σ j = i n ∣ a i + a j − 1000 ∣ Σ_{j=i}^{n}∣ai+aj−1000∣ Σj=inai+aj1000, 那对{ a a a} 序列进行一个排序,在这个{ a a a} 的顺序序列中,总有一个值是 a i + a j − 1000 ai+aj−1000 ai+aj1000 正负性的分界数(除非都大于等于0或者都小于0,那就更简单了),然后对这个分界前段和后段分类讨论即可,这个分界数我们通过二分查找找到,code的时候注意边界条件啥的不然很容易出错,思路还是简单的。(当然这题用方法一最合适)

代码(solution2)

void solveH()
{
    int N;
    cin >> N;
    vector a(N);
    for (int i = 0; i < N; i++)
        cin >> a[i];
    vector last(N + 1, 0);
    sort(a.begin(), a.end());
    last[N] = 0;
    last[N - 1] = a[N - 1];
    for (int i = N - 2; i >= 0; i--)
    {
        last[i] = last[i + 1] + a[i];
    }

    int pos = 0;
    ll ans = 0;
    //cout<<"pos="< 0)
        {
            tmp += abs(a[i] * (pos + 1 - i) + last[i] - last[pos + 1] - 1000 * (pos + 1 - i));
            //cout<2022牛客寒假算法基础集训营1解题记录_第3张图片

方法解析

  概率期望题,为了方便考虑,最好的方法是:对于n个up主,每个up主每句都以一定概率p选择唱或者不唱。那么对于每一句,唱失败的概率是 p n + ( 1 − p ) n p^n + (1-p)^n pn+(1p)n ,为了最小化这个概率 p p p 1 2 \frac{1}{2} 21(不会求的话可以猜出来),那么就知道了每一句唱成功的概率,求期望的话乘句子数 m m m 即可: m × 2 n − n 2 n m \times \frac{2^n - n}{2^n} m×2n2nn ,需要用逆元防止模出错。

代码

const ll modI = 1e9 + 7;
//利用快速幂可以用来快速求a^(p-2)
ll qpow(ll base, ll power, ll mod)
{
    ll ans = 1;
    base %= mod;
    while (power > 0)
    {
        if (power & 1)
        { //指数为奇数,先乘
            ans = ans * base % mod;
        }
        power >>= 1;                //指数取半
        base = (base * base) % mod; //基数平方
    }
    return ans;
}
//求a%p的逆元
ll calInv(ll a, ll p)
{
    return qpow(a, p - 2, p);
}
void solveI()
{
    int T;
    cin >> T;
    while (T--)
    {
        int n, m;
        cin >> n >> m;
        // m * (1 - 2 * 2^n)  = m * (2^n - 2) / 2^n
        cout << m * (qpow(2, n, modI) - 2) % modI * calInv(qpow(2, n, modI), modI) % modI << endl;
    }
}

Problem J 小朋友做游戏

链接:https://ac.nowcoder.com/acm/contest/23106/J
来源:牛客网

题意

签到题,闹腾小朋友最多选一半。

代码

bool cmp(int a, int b) { return a > b; }
void solveJ()
{
    int T;
    cin >> T;
    while (T--)
    {
        int A, B, n;
        cin >> A >> B >> n;
        vector va(A);
        vector vb(B);

        for (int i = 0; i < A; i++)
            cin >> va[i];
        for (int i = 0; i < B; i++)
            cin >> vb[i];
        sort(va.begin(), va.end(), cmp);
        sort(vb.begin(), vb.end(), cmp);

        if (2 * A < n)
        {
            cout << -1 << endl;
            continue;
        }
        int a, b;
        a = 0;
        b = 0;
        ll ans = 0;

        for (int i = 0; i < n; i++)
        {
            if (a < A && (va[a] >= vb[b] || b == B))
            {

                //cout<<"1numa++ ans+="<double dist(int x1, int x2, int y1, int y2)
{
    return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
}
void solveL()
{
    int T;
    cin >> T;
    while (T--)
    {
        int n;
        cin >> n;
        int x0 = 0;
        int y0 = 0;
        double ans = 0.0f;
        for (int i = 0; i < n; i++)
        {
            char c;
            cin >> c;
            if (c == 'L')
            {
                x0--;
            }
            else if (c == 'R')
            {
                x0++;
            }
            else if (c == 'U')
            {
                y0++;
            }
            else if (c == 'D')
            {
                y0--;
            }
            ans = max(ans, dist(0, x0, 0, y0));
        }
        printf("%.10f\n", ans);
    }
}


你可能感兴趣的:(水赛记录,算法,动态规划)