牛客小白月赛40 题解

A
dd在玩数字游戏,首先他拿到一个x
当x不为零时进行如下操作
如果二进制x中有奇数个1,则x二进制形式下最低位取反(即0变成1,1变成0)
如果二进制x中有偶数个11,则x二进制形式下非前导零最高位取反
询问对于一个x,操作几次后变为零

大力讨论即可

#include <iostream>
using namespace std;

int T, x;

int cnt(int x) {
    int res = 0;
    while (x) {
        x -= x & -x;
        res++;
    }
    return res;
}

int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &x);
        if (!x) {
            puts("0");
            continue;
        }

        int n = cnt(x >> 1), m = x & 1;
        if ((n + m) % 2 == 1) {
            if ((m == 0 && (n % 2 == 0)) || (m == 1 && (n % 2 == 1))) printf("%d\n", 2 * n);
            else printf("%d\n", 2 * n + 1);
        } 
        else {
            if ((m == 0 && (n % 2 == 0)) || (m == 1 && (n % 2 == 1))) printf("%d\n", 2 * n);
            else printf("%d\n", 2 * n - 1); 
        }
    }
    return 0;
}

B
dd在玩跳格子游戏,具体游戏规则如下,
n个格子呈环形分布,顺时针方向分别标号为1∼n,其中1和n相邻,每个格子上都有一个正整数a[i],玩家可以选择一个点作为起点开始跳n下,第i次跳跃,玩家只可以选择当前位置左边或右边最近且尚未被跳跃过的位置进行一次跳跃,并获得i×a[p]的得分,其中p为第i次跳跃的位置。
dd很鸡贼,想赢又不想动脑子,她希望你能给她规划路线以确保她的胜利

考虑逆推:
设f[l][r]为从[l,r]中一点开始,跳完区间[l,r]的最大得分,那么显然
f[l][r] = max(f[l+1][r] + i * a[l], f[l][r-1] + i * a[r]);
其中i为区间的长度
对环形的处理:令a[i+n]=a[i],枚举区间[i,i+n-1]取最大值即可

似乎记忆化搜索很好写qwq

#include <iostream>
using namespace std;
const int maxn = 4e3 + 10;

int n, a[maxn]; 
int f[maxn][maxn]; //记忆化搜索

int dp(int l, int r) {
    if (l == r) return a[l];
    if (f[l][r]) return f[l][r];
    int i = r - l + 1;
    return f[l][r] = (max(dp(l + 1, r) + i * a[l], dp(l, r - 1) + i * a[r]));
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        a[i + n] = a[i];
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) 
        ans = max(ans, dp(i, i + n - 1));
    printf("%d", ans);
    return 0;
}

C
image

首先注意到>=k一定包含了等于k的情况,因此只需判断是否存在长度为k的相同子串即可
我们可以开两个bool数组分别记录x和y的某个k位子串存不存在,然后暴力枚举即可

#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;

int n, k, ans;
int b1[10000], b2[10000], t[10000];

void ins(int x, int b[]) {
    memset(b, 0, sizeof(int) * (1 << k + 1));
    int cnt = 0;
    while (x) {
        t[++cnt] = x & 1;
        x >>= 1;
    }
    for (int i = 1; i * 2 <= cnt; i++) swap(t[i], t[cnt - i + 1]);
    for (int i = 1; i + k - 1 <= cnt; i++) {
        int p = 0;
        for (int j = i; j <= i + k - 1; j++) {
            p <<= 1;
            p += t[j];
        }
        b[p] = 1;
    }
}

int main() {
    scanf("%d%d", &n, &k);
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j < i; j++) {
            if ((int)log2(i) + 1 < k || (int)log2(j) + 1 < k) continue;
            ins(i, b1);
            ins(j, b2);
            bool flag = false;
            for (int p = 0; p < (1 << k); p++) {
                if (b1[p] && b2[p]) {
                    flag = true;
                    break;
                }
            }
            ans += flag;
        }
    }
    printf("%d", ans);
    return 0;
}

D
DD给了dd一个由小写英文字母组成的字符串,但是dd觉得这个串太丑,dd觉得一个优美的字符串应该满足任意相邻字符都不相等,她想知道把给定字符串变成一个优美的字符串最少需要插入多少个字符,你只要告诉她,插入最少字符后的串的长度

签到题

#include <iostream>
#include <cstring>
using namespace std;
int T;
char s[100007];
int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%s", s);
        int ans = strlen(s);
        for (int i = 0; s[i] != '\0'; i++) {
            if (i > 0 && s[i] == s[i - 1])
                ans++;
        }
        printf("%d\n", ans);
    }
    return 0;
}

E
dd当上了宣传委员,开始组织迎新晚会,已知班里有n个同学,每个同学有且仅有一个擅长的声部,把同学们分成恰好m组,为了不搞砸节目,每一组里的同学都必须擅长同一个声部,当然,不同组同学擅长同一个声部的情况是可以出现的,毕竟一个声部也可以分成好几个part进行表演,但是他不希望出现任何一组的人过多,否则可能会导致场地分配不协调,也就是说,她希望人数最多的小组的人尽可能少,除此之外,对组内人员分配没有其他要求,她希望你告诉她,这个值是多少,如果无法顺利安排,请输出-1

经典二分答案

注意:
当越大越容易满足条件,求第最小的一个时

 while (l < r) {
        int mid = (l + r) / 2;
        if (check(mid)) r = mid; else l = mid + 1;  
    }

当越小越容易满足条件,求最大的一个时

 while (l < r) {
        int mid = (l + r + 1) / 2;
        if (check(mid))  l = mid; else r = mid - 1;  
    }

本题代码

#include <iostream>
using namespace std;
const int maxn = 2e5 + 10;
int n, m, cnt, a[maxn], b[maxn], l = 1, r;

int ceil(int x, int y) {
    if (x % y == 0) return x / y;
    else return x / y + 1;
}

bool check(int x) {
    int res = 0;
    for (int i = 1; i <= n; i++) res += ceil(b[i], x);
    return res <= m;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        b[a[i]]++;
        if (b[a[i]] == 1) cnt++;
        r = max(r, b[a[i]]);
    }
    if (cnt > m) {
        printf("-1");
        return 0;
    }

    while (l < r) {
        int mid = (l + r) / 2;
        if (check(mid)) r = mid; else l = mid + 1;   
    }
    printf("%d", l);
    return 0;
}

F
image
被我用BFS水过了。。。

#include <iostream>
#include <queue>
using namespace std;
const int maxn = 1e4 + 10;
#define pos first
#define step second
bool vis[maxn];
int n, a[maxn];
queue<pair<int, int> > q;
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    if (a[1] > 0) {
        for (int i = 1 + 1; i <= 1 + a[1] && i <= n; i++) {
            q.push({i, 1});
        }
    } else {
        printf("-1");
        return 0;
    }

    vis[1] = true;
    while (!q.empty()) {
        pair<int, int> u = q.front(); q.pop();
        if (u.pos == n) {
            printf("%d", u.step);
            return 0;
        }
        if (vis[u.pos]) continue;
        vis[u.pos] = true;

        if (u.pos + a[u.pos] < 1) continue;
        if (a[u.pos] > 0) {
            if (u.pos + a[u.pos] >= n) {
                printf("%d", u.step + 1);
                return 0;
            }
            for (int i = u.pos + 1; i <= u.pos + a[u.pos] && i <= n; i++) {
                q.push({i, u.step + 1});
            }
        } else {
            for (int i = 1; i <= u.pos + a[i]; i++) {
                q.push({i, u.step + 1});
            }
        }
    }
    printf("-1");
    return 0;
}

G
image
简单的双指针

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 2e6 + 10;
int n, p, a[maxn];
int main() {
    scanf("%d%d", &n, &p);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    sort(a + 1, a + 1 + n);
    p = p * 2;
    int r = 1, ans = 0;
    for (int l = 1; l <= n; l++) {
        while (a[l] + p >= a[r] && r <= n) r++;
        ans = max(ans, r - l);
        if (r > n) break;
    }
    printf("%d", ans);
    return 0;
}

H
给定一个有n个元素的多重集S,有m个询问,对于每个询问,给出一个整数x,问是否能选择S的一个非空子集,满足这个子集的gcd等于x,当集合只有一个数时,设这个集合的gcd就等于这个数。

对于给定的x,我们在S中选择的数一定是x的倍数,而且选得越多,它们的gcd越有可能是x
用gcdx[x]表示把选出x的所有倍数进行gcd的结果,如果gcdx[x]等于x,则可以选出gcd等于x的集合

#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 2e6 + 10;

int gcd(int x, int y) {
    return y ? gcd(y, x % y) : x;
}

int T, n, m;
int a[maxn];
int gcdx[maxn];

int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);

        memset(gcdx, 0, sizeof(int) * (n + 1));
        memset(a, 0, sizeof(int) * (n + 1));

        for (int i = 1; i <= n; i++) {
            int x;
            scanf("%d", &x);
            a[x] = 1;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = i; j <= n; j += i) {
                if (a[j]) gcdx[i] = gcd(gcdx[i], j); 
            }
        }

        while (m--) {
            int x;
            scanf("%d", &x);
            gcdx[x] == x ? printf("YES\n") : printf("NO\n");
        }
    }
    return 0;
}

注意这个循环,根据调和级数,其复杂度为n*ln(n)

for (int i = 1; i <= n; i++) {
    for (int j = i; j <= n; j += i) {
         if (a[j]) gcdx[i] = gcd(gcdx[i], j); 
    }
}

I
image
注意到n不超过10,next_permutation即可

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 20;

int n, ans, a[maxn], p[maxn];

bool check() {
    for (int i = 1; i <= n; i++) {
        if (a[p[i]] == p[i]) continue;
        for (int j = 1; j < i; j++) 
            if (p[j] == a[p[i]]) return false;
    }
    return true;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]), p[i] = i;
    do {
        if (check()) ans++;
    } while (next_permutation(p + 1, p + 1 + n));
    printf("%d", ans);
    return 0;
}
posted @ 2021-11-06 17:37  _vv123  阅读(2329)  评论(0编辑  收藏  举报