【非官方题解】2022牛客寒假算法基础集训营1

2022牛客寒假算法基础集训营1_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ

目录

A-九小时九个人九扇门

B-炸鸡块君与FIFA22

C-Baby's first attempt on CPU

D-牛牛做数论

E-炸鸡块君的高中回忆

F-中位数切分

G-ACM is all you need

H-牛牛看云

I-B站与各唱各的

J-小朋友做游戏

K-冒险公社

L-牛牛学走路


A-九小时九个人九扇门

原题链接:https://ac.nowcoder.com/acm/contest/23106/A

解题思路:注意数论中原根的性质:一个数的原根是该数对 9 取模的余数,余数为 0 时原根为 9。然后问题就转化成求不同数的组合之和对 9 取模分别为 0~9 的方案数。可以发现这是0/1背包的变形,可以将dp[i][j]就可以表示成考虑前 i 个人的原根为 j 的方案数(要注意取模之后为 0 的表示原根为 9,其它的值与原根相同)。我们的状态转移方程就可以表示为\begin{cases} dp[i][(j + a[i]) % 9] = dp[i][(j + a[i]) % 9] + dp[i - 1][j]\\ dp[i][j] = dp[i][j] + dp[i - 1][j] \end{cases}

这样要求的答案就是 dp[n][1]dp[n][8]以及dp[n][0] - 1(这里减1是因为dp[n][0]中包括所有人都不选上的情况)

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 998244353;
const int N = 100005;
int a[N];
int dp[N][9];

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int n;
    cin >> n;
    dp[0][0] = 1;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        a[i] %= 9;
    }
    for(int i = 1; i <= n; i++){
        for(int j = 0; j < 9; j++){
            dp[i][(j + a[i]) % 9] = (dp[i][(j + a[i]) % 9] + dp[i - 1][j]) % mod;
            dp[i][j] = (dp[i][j] + dp[i - 1][j]) % mod;
        }
    }
    for(int i = 1; i < 9; i++) cout << dp[n][i] << ' ';
    cout << dp[n][0] - 1 << endl;
    return 0;
}

B-炸鸡块君与FIFA22

原题链接:https://ac.nowcoder.com/acm/contest/23106/B

解题思路:根据数据量可以发现暴力模拟一定会T,而又要求多个询问下区间内符合条件的值,这就可以用到倍增的思想。题中分数是 3 的整数倍时失败不掉分的另一种表述为起始分数在模 3 的意义下相等定义st[k][i][j]表示初始分数为 k 的情况下经历[i, i + 2^j - 1]一段游戏后分数的变化量。根据倍增的思想进行预处理 st 表:先根据题目的要求预处理出 j = 0 的情况,然后按照转移方程st[k][i][j] = st[k][i][j - 1] + st[(k + st[k][i][j - 1]) % 3][i + 2^{j - 1}][j - 1]进行预处理。然后对于每次询问,从 l 跳 2 的若干次幂到 r,跳的过程中根据分数模 3 的结果来访问 st 表中的值。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 200005;
int st[3][N][21];
char str[N];

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int n, q;
    scanf("%d%d", &n, &q);
    scanf("%s", str);
    for(int i = 0; i < n; i++){
        if(str[i] == 'W') st[0][i][0] = st[1][i][0] = st[2][i][0] = 1;
        if(str[i] == 'L') st[1][i][0] = st[2][i][0] = -1;
    }
    for(int j = 1; (1 << j) <= n; j++){
        for(int i = 0; i + (1 << j) - 1 < n; i++){
            for(int k = 0; k < 3; k++){
                st[k][i][j] = st[k][i][j - 1] + st[(k + st[k][i][j - 1]) % 3][i + (1 << (j - 1))][j - 1];
            }
        }
    }
    while(q--){
        int l, r, s;
        scanf("%d%d%d", &l, &r, &s);
        int len = r - l + 1, i = 0;
        while(l <= r){
            if((len >> i) & 1){
                s += st[s % 3][l - 1][i];
                l += (1 << i);
            }
            i++;
        }
        printf("%d\n", s);
    }
    return 0;
}

C-Baby's first attempt on CPU

原题链接:https://ac.nowcoder.com/acm/contest/23106/C

解题思路:题目大意是每两条先写后读相关的语句之间要有 3 行空语句,求要插入的最少空语句数。注意题目a_{i, j}的定义,它表示第 i 行和第 i - j 行是否是相关的(为 1 表示相关,为 0 表示不相关)。由于不能直接改变原来的语句序号,有一种方法是将修改后的总语句数求出来减去原语句数即可。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 105;
int a[N];

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++){
        a[i] = a[i - 1] + 1;
        for(int j = 0; j < 3; j++){
            int x;
            cin >> x;
            if(x) a[i] = max(a[i], a[i - j - 1] + 4);
        }
    }
    cout << a[n] - n << endl;
    return 0;
}

D-牛牛做数论

原题链接:https://ac.nowcoder.com/acm/contest/23106/D

解题思路:首先需要熟悉下面两个欧拉函数的定理:

                (1)如果 p 是素数,那么\phi(p) = p - 1。反之,如果 p 是正整数且满足\phi(p) = p - 1,那么 p 是素数;

                (2)设n = p_{1}^{a_{1}}p_{2}^{a_{2}}...p_{k}^{a_{k}}为正整数 n 的素幂因子分解,那么

                                        \phi (n) = n(1 - \frac{1}{p_{1}})(1 - \frac{1}{p_{2}})...(1 - \frac{1}{p_{k}})

                  因此H(x) = \frac{\phi (x)}{x}

                  对于问题1,要想 H(x) 最小,那么根据H(x) = (1 - \frac{1}{p_1})(1 - \frac{1}{p_2})...(1 - \frac{1}{p_k}),则素数的种数越多越好;

                  对于问题2,要想 H(x) 最大,那么根据H(p) = \frac{p - 1}{p},则这个数为素数且越大越好。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int p[] = {2, 3, 5, 7, 11, 13, 17, 19, 23};

bool is_prime(int x){
    for(int i = 2; i * i <= x; i++){
        if(x % i == 0) return false;
    }
    return true;
}

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        if(n == 1) cout << -1 << endl;
        else{
            ll res = 1;
            for(int i = 0; i < 9; i++){
                if(res * p[i] > n) break;
                res *= p[i];
            }
            while(!is_prime(n)) n--;
            cout << res << ' ' << n << endl;
        }
    }
    return 0;
}

E-炸鸡块君的高中回忆

原题链接:https://ac.nowcoder.com/acm/contest/23106/E

解题思路:看上去就是找规律推公式的题目(很明显模拟会超时),公式为ans = \begin{cases} \frac{n - 1}{m - 1} * 2 - 1, & (n - 1) % (m - 1) = 0\\ (\frac{n - 1}{m - 1} + 1) * 2 - 1, & (n - 1)%(m - 1) \neq 0 \end{cases}(注意特判 m = 1 时:n = 1 或 n > 1)

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
ll n, m;

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t;
    cin >> t;
    while(t--){
        cin >> n >> m;
        if(m == 1){
            if(n == 1) cout << 1 << endl;
            else cout << -1 << endl;
        }
        else{
            cout << ((n - 1) / (m - 1) + ((n - 1) % (m - 1) != 0)) * 2 - 1 << endl;
        }
    }
    return 0;
}

推不出公式怎么办?那就可以放弃了。

还有一种方法是二分答案,设进入的总次数为 t ,最后一次进入时的人数为 x ,那么一定有x = n - (t - 1)(m - 1) \leqslant m( x > m 说明第 t 次不是最后一次进入),且总用时为 2 * t - 1。这样就可以二分寻找合适的 t ,时间复杂度O(t * log n)就不会T了。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
ll n, m;

bool check(ll x){
    if(n - (x - 1) * (m - 1) <= m) return true;
    else return false;
}

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t;
    cin >> t;
    while(t--){
        cin >> n >> m;
        if(m == 1){
            if(n == 1) cout << 1 << endl;
            else cout << -1 << endl;
        }
        else{
            ll l = 1, r = 1000000000;
            while(l < r){
                ll mid = l + r >> 1;
                if(check(mid)) r = mid;
                else l = mid + 1;
            }
            cout << 2 * r - 1 << endl;
        }
    }
    return 0;
}

F-中位数切分

原题链接:https://ac.nowcoder.com/acm/contest/23106/F

解题思路:多试几个例子就会发现,切分的段数与所给的序列顺序是无关的,仅与序列中大于等于所给定的数的个数有关(证明过程在官方题解中有)。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 100005;
int a[N];

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t;
    scanf("%d", &t);
    while(t--){
        int n, m, ans = 0;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++){
            scanf("%d", &a[i]);
            if(a[i] >= m) ans++;
            else ans--;
        }
        if(ans <= 0) printf("-1\n");
        else printf("%d\n", ans);
    }
    return 0;
}

G-ACM is all you need

原题链接:https://ac.nowcoder.com/acm/contest/23106/G

解题思路:由于对整体作f_i = |f_i - b| + b的变换,因此 +b 的操作可以忽略,即简化为f_i = |f_i - b|的变换,对于三个数出现的大小情况,我们可以分为三种:第一种是两边大中间小,即符合题目要求的样子;第二种是两边小中间大;第三种是两边的有大有小,中间的比小的大,比大的小。分别求出满足情况的 b 的区间,排序后寻找区间覆盖最多的一段即可。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
#include <vector>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 100005;
int f[N];
vector<pair<int, int> > vec;

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t;
    cin >> t;
    while(t--){
        int n, now = 0;
        cin >> n;
        for(int i = 1; i <= n; i++) cin >> f[i];
        for(int i = 2; i < n; i++){
            if(f[i] < f[i - 1] && f[i] < f[i + 1]){
                now++;
                int x = (f[i] + min(f[i - 1], f[i + 1]) + 1) / 2;
                vec.push_back({x, -1});
            }
            else if(f[i] > f[i - 1] && f[i] > f[i + 1]){
                int x = (f[i] + max(f[i - 1], f[i + 1])) / 2 + 1;
                vec.push_back({x, 1});
            }
            else if(f[i] > min(f[i - 1], f[i + 1]) && f[i] < max(f[i - 1], f[i + 1])){
                int x = (f[i] + min(f[i - 1], f[i + 1])) / 2 + 1;
                vec.push_back({x, 1});
                x = (f[i] + max(f[i - 1], f[i + 1]) + 1) / 2;
                vec.push_back({x, -1});
            }
        }
        sort(vec.begin(), vec.end());
        int pos = 0, i, j, ans = now, len = vec.size();
        while(pos < len){
            for(i = pos; i < len; i++){
                if(vec[i].first != vec[pos].first) break;
                now += vec[i].second;
            }
            ans = min(ans, now);
            pos = i;
        }
        cout << ans << endl;
        vec.clear();
    }
    return 0;
}

H-牛牛看云

原题链接:https://ac.nowcoder.com/acm/contest/23106/H

解题思路:在输入数组时将每个数-500可以将题目的式子中的-1000给去掉,原题就变成求\sum_{i = 1}^{n}\sum_{j = i}^{n}|a_i + a_j|,然后将数组进行排序,每个数二分找它的相反数的位置,在该位置之前的数和该数相加一定小于(等于)0,在该位置之后的数和该数相加一定大于0,这样分两段分别取绝对值再相加就可以算出结果。这里二分可以用lower_bound()或者upper_bound(),总时间复杂度为O(nlongn)

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 1000005;
ll a[N], s[N];

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    ll n, sum = 0;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        a[i] -= 500;
    }
    sort(a + 1, a + n + 1);
    for(int i = 1; i <= n; i++){
        s[i] = s[i - 1] + a[i];
    }
    for(int i = 1; i <= n; i++){
        if(a[i] < 0){
            int pos = upper_bound(a + i, a + n + 1, -a[i]) - a - 1;
            sum += abs(a[i] * (pos - i + 1) + (s[pos] - s[i - 1])) + abs(a[i] * (n - pos) + (s[n] - s[pos]));
        }
        else{
            sum += a[i] * (n - i + 1) + s[n] - s[i - 1];
        }
    }
    cout << sum << endl;
    return 0;
}

I-B站与各唱各的

原题链接:https://ac.nowcoder.com/acm/contest/23106/I

解题思路:可以发现每个句子之间是相互独立的,因此有E(mX) = mE(X),设每个人每句以p_i的概率决定唱或不唱,总的失败概率为\prod_{i= 1}^{n}p_i + \prod_{i = 1}^{n}(1 - p_i),由于 n 位up主足够聪明,因此要失败概率最小,即当p_1 = p_2 = ... = p_n = \frac{1}{2}时,最小失败概率为\frac{1}{2^{n - 1}}。那么成功概率就为\frac{2^{n - 1} - 1}{2^{n - 1}},期望为m * \frac{2^{n - 1} - 1}{2^{n - 1}}

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;

ll quick_pow(ll a, ll b){
    ll res = 1;
    while(b){
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

ll inv(ll x){
    return quick_pow(x, mod - 2);
}

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t;
    scanf("%d", &t);
    while(t--){
        ll n, m;
        scanf("%lld%lld", &n, &m);
        ll pn = quick_pow(2, n - 1);
        printf("%lld\n", (pn - 1) % mod * inv(pn) % mod * m % mod);
    }
    return 0;
}

J-小朋友做游戏

原题链接:https://ac.nowcoder.com/acm/contest/23106/J

解题思路:要求仅为两个闹腾的小朋友不能相邻,因此闹腾的小朋友的总数是不受 n 的约束的,只有安静的小朋友总数受约束,即A \geqslant n - \left \lfloor \frac{n}{2} \right \rfloor时才能安排进行游戏。而又要求总幸福度最大,因此贪心从幸福度最大的小朋友开始选。(注意闹腾的小朋友最多只能选 n / 2 个)

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 10005;
int an[N], nao[N];

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t;
    cin >> t;
    while(t--){
        int a, b, n;
        cin >> a >> b >> n;
        for(int i = 0; i < a; i++) cin >> an[i];
        for(int i = 0; i < b; i++) cin >> nao[i];
        if(a < n - n / 2){
            cout << -1 << endl;
            continue;
        }
        sort(an, an + a);
        sort(nao, nao + b);
        int i = a - 1, j = b - 1, cnt = 0, cntj = 0;
        ll sum = 0;
        while(cnt < n){
            if(cntj + 1 <= n / 2){
                if(an[i] >= nao[j]) sum += an[i--];
                else{
                    sum += nao[j--];
                    cntj++;
                }
            }
            else sum += an[i--];
            cnt++;
        }
        cout << sum << endl;
    }
    return 0;
}

K-冒险公社

原题链接:https://ac.nowcoder.com/acm/contest/23106/K

解题思路:如果注意到这题是dp题的话就很简单了,设dp[i][j][k][l]表示考虑到前 i 个岛,第 i - 2i - 1i三个岛中分别放 jkl岛时的最大绿岛数(j,k,l\in [0, 2],其中0表示绿岛,1表示红岛,2表示黑岛),状态转移为dp[i][j][k][l] = max(dp[i][j][k][l], dp[i - 1][t][j][k] + (l == 0)),其中 t 为第 i - 3 个岛的颜色。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
#include <set>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 100005;
int dp[N][3][3][3];    // 0表示绿岛,1表示红岛,2表示黑岛,后三维分别表示i,i - 1, i - 2岛的颜色
char s[N];

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int n;
    cin >> n;
    cin >> s + 1;
    memset(dp, -0x3f, sizeof dp);
    int ans = -1;
    for(int i = 3; i <= n; i++){
        for(int j = 0; j < 3; j++){
            for(int k = 0; k < 3; k++){
                for(int l = 0; l < 3; l++){
                    int c[3] = {0};    // c[0]表示绿岛数,c[1]表示红岛数,c[2]表示黑岛数
                    c[j]++, c[k]++, c[l]++;
                    if(s[i] == 'G' && c[0] <= c[1]) continue;
                    if(s[i] == 'R' && c[1] <= c[0]) continue;
                    if(s[i] == 'B' && c[0] != c[1]) continue;
                    if(i == 3) dp[i][j][k][l] = c[0];
                    if(i >= 4){
                        for(int t = 0; t < 3; t++){
                            dp[i][j][k][l] = max(dp[i][j][k][l], dp[i - 1][t][j][k] + (l == 0));
                        }
                    }
                    if(i == n) ans = max(ans, dp[i][j][k][l]);
                }
            }
        }
    }
    cout << ans << endl;
    return 0;
}

L-牛牛学走路

原题链接:https://ac.nowcoder.com/acm/contest/23106/L

解题思路:签到题,取每走一步后与原点距离的最大值即可。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        string s;
        cin >> s;
        int len = s.size();
        double x = 0, y = 0, dis = 0;
        for(int i = 0; i < len; i++){
            if(s[i] == 'U') y++;
            if(s[i] == 'D') y--;
            if(s[i] == 'L') x--;
            if(s[i] == 'R') x++;
            dis = max(dis, sqrt(x * x + y * y));
        }
        printf("%.12lf\n", dis);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值