「CodeNote」 牛客小白25题解

Posted by Dawn-K's Blog on June 26, 2020

牛客小白25

[toc] 通过数量 9/10 C和J题值得思考 比赛链接

A

牛可乐准备和 n 个怪物厮杀. 已知第 i 个怪物的血量为 a_i . 牛可乐有两个技能: 第一个技能是蛮牛冲撞, 消耗 1 mp , 可以对任意单体怪物造成 1 点伤害. 第二个技能是蛮牛践踏, 消耗 x mp , 可以对全体怪物造成 1 点伤害. 牛可乐想知道, 将这些怪物全部击杀, 消耗 mp 的最小值的多少?

题解: 当x < n的时候, 都是划算的, 否则就单体击杀(x==n是都一样) 所以我们对怪物的血量进行排序, 找到第x大的怪物, 比他大的怪物, 都要一个一个消灭. 所以可以计算出需要几次aoe, 几次平a, 算出答案即可.

B

这个需要补, 不太会

C

这个题很棒, 答案挺出乎意料.

题意:

你是一个白魔法师. 现在你拿到了一棵树, 树上有 n 个点, 每个点被染成了黑色或白色. 你可以释放一次魔法, 将某个点染成白色.(该点不一定是黑色点, 也可以是白色点) 现在释放魔法后要保证最大的白色点连通块尽可能大. 请求出最大白色连通块的大小. 注: 所谓白色连通块, 指这颗树的某个连通子图, 上面的点全部是白色.

n<=1e5 题解: 这个题解我一开始没想到.

其实树有个很好的性质, 就是一个点最多与周围三个点发生联系. 也就是其父亲+左右儿子. 以为只能染白一个, 所以就枚举是哪个黑节点被染白就可以了. 对于每个黑节点, 其如果被染白, 无非就是让父亲和儿子节点发生联通. 那么如何加快联通呢? 答案就是首先用并查集预处理这棵树, 将白连通块都缩成一个点(也不是真的缩点, 就是任意一个白节点都知道自己所在的连通块的大小(此时可以先更新一下最大值, 也就是染色之前的最大值)). 然后对于每个被枚举的黑节点, 他有可能让父亲儿子, 或者儿子儿子之间的连通块合并成更大的, 所以就查询一下父亲和儿子(如果是白节点的话)所在的连通块大小, 然后将其合并, 更新最大值即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// contest5600C
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 1000000007
#define maxn 100000 + 100
vector<int> v[maxn];
int f[maxn];
int value[maxn];
int Find(int x) {
    if (x != f[x]) {
        f[x] = Find(f[x]);
    }
    return f[x];
}
void Mix(int x, int y) {
    int fx = Find(x);
    int fy = Find(y);
    if (fx != fy) {
        f[fx] = fy;
        value[fy] += value[fx];
        value[fx] = 0;
    }
}
int main() {
    int n;
    cin >> n;
    for (int i = 0; i <= n; ++i) {
        f[i] = i;
    }
    string s;
    cin >> s;
    for (int i = 0; i < n - 1; ++i) {
        int x, y;
        cin >> x >> y;
        --x, --y;
        v[x].push_back(y);
        v[y].push_back(x);
    }
    for (int i = 0; i < n; ++i) {
        if (s[i] == 'W') {
            value[i] = 1;
        } else {
            value[i] = 0;
        }
    }
    for (int i = 0; i < n; ++i) {
        if (s[i] == 'W') {
            for (auto j : v[i]) {
                if (s[j] == 'W') {
                    Mix(i, j);
                }
            }
        }
    }
    int ans = 0;
    for (int i = 0; i < n; ++i) {
        if (s[i] == 'W') {
            ans = max(ans, value[Find(i)]);
        }
    }
    for (int i = 0; i < n; ++i) {
        if (s[i] == 'B') {
            int res = 1;
            for (auto j : v[i]) {
                res += value[Find(j)];
            }
            ans = max(ans, res);
        }
    }
    cout << ans << endl;
    return 0;
}

D

题意: 王子连接的国服终于上线啦~ 已知王子连接的抽卡系统如下: 共有 n 个卡池, 第 i个卡池共有 a_i 种卡, 每张卡的出货率都是相等的(也就是说该卡池单次抽卡, 每种卡出货率是 1/a_i). 第 i 个卡池中, 你有 b_i 种卡是自己很想要的. 现在的问题是, 如果每个卡池里都单抽一次, 能抽到自己想要的卡的概率是多少? 可以证明, 这个概率一定可以写成 形式的分数. 最后输出该分数在模 10^9+7意义下的值就可以了. 即输出满足 b*x%1000000007=a 的最小非负整数 .

不难看出, 最后的答案就是

\(1- \prod_{0}^{n-1}\left(\frac{a_i-b_i}{a_i}\right)\mod (1e9+7)\) 然后将分数化成整数, 使用一下乘法逆元即可.

E

题目:

牛牛拿到了一个字符串. 他每次”点击”, 可以把字符串中相邻两个相同字母消除, 例如, 字符串”abbc”点击后可以生成”ac”. 但相同而不相邻、不相同的相邻字母都是不可以被消除的. 牛牛想把字符串变得尽可能短. 他想知道, 当他点击了足够多次之后, 字符串的最终形态是什么? 字符串长度<=3e5

思路: 做过类似的, 用栈的思想即可, 如果字符串当前的字符和栈顶的字符一样, 那么就弹出栈顶, 丢弃当前字符. 否则(也包括栈为空的情况)就将当前字符压栈.

F

牛妹作为偶像乐队的主唱, 对自己的知名度很关心. 她平时最爱做的事就是去搜索引擎搜自己的名字, 看看别人对自己的评价怎么样. 这天, 她打开了一个”偶像评分系统”, 上面有很多人给她打分. “偶像评分系统”的分数有1分、2分、3分、4分和5分. 给牛妹评分的人有 n 个. 但其中有 m 个人把分数隐藏了, 牛妹并不能看到这些人给她打的分数. 牛妹想知道, 已知这些信息的情况下, 自己得到的平均分数的最大可能和最小可能分别是多少? m, n<=2e5

思路: 假设m个人都打5分或者1分, 算出平均分即可.

G

牛能作为一个学霸, 非常擅长解方程. 有一天, 他拿到了一个方程: x^a+blnx=c 牛能当然一下子就解出了这个方程. 但他想考考聪明的你, 这个方程的解的多少?

1< a< 3 b, c < 1e9

思路: 其实很简单, 我们观察 x^a+blnx-c , 发现x->0的时候, 这个式子小于0, 发现x->1e9的时候, 这个式子一定大于0

我们直接二分一下就好了.

H

输出字符串中出现次数最多的字母即可

I

题目: 牛牛在玩一个游戏: 一共有n行m列共nm个方格, 每个方格中有一个整数. 牛牛选择一个方格, 可以得到和这个方格同行、同列的所有数之和的得分. 例如: 对于一个22的方格: 1 2 3 4 牛牛选择每个方格的得分如下: 6 7 8 9 因为1+2+3=6, 1+2+4=7, 1+3+4=8, 2+3+4=9. 现在牛牛想知道下一步选择每个格子的得分情况, 你可以帮帮他吗?

思路: 预处理每一行和每一列的和, 然后枚举所有的点位, 将对应的行列值加起来, 统计最大值即可

J

给一个数组, 数组内有 个正整数. 求这些数任取3个数异或运算后求和的值. 也就是说, 取一共$C_{n}^{3}$ 个三元组, 计算这些三元组内部异或, 之后求和.(具体操作可以见样例描述) 由于该值可能过大, 输出其对 1e9+7 取模的值.

3< n <2e5 a_i<1e18

思路:

题目的意思是枚举所有的三元组求和, 实际上直接枚举的话很困难. 我们考虑算贡献.

对于两个数的异或, 其实可以看成逐位异或之后加权求和, 那对于所有的数也可以如此拆分. 我们把每一个数都拆成64位, 然后我们就得到了对于每一位, 有多少个数字这一位上是1, 有多少是0

对于每一位, 都是从n中挑3个数异或, 也就是说, 从n个数中抽取是三个数, 对于当前的这个位, 会有四种情况: 111 110 100 000. 其异或的结果分别是 1 0 1 0 . 所以我们只需要记录111和100这两种情况的贡献就行.

如果对于第j位, 有x个数此位为1, 有n-x个数此位为0. 那么, 111 的情况总共有 x*(x-1)*(x-2)/6 种, 100的情况总共有 x*(n-x)*(n-x-1) 种. 然后我们按照每个位的贡献加权一下, 就得到了最后的答案.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <bits/stdc++.h>
using namespace std;

#define ll long long

ll a[200000 + 100];
ll bit[100];
int mod = 1000000007;
ll cal(ll x, ll y) {
    ll ans = 0;
    // 1 1 1
    ans = (ans + (x * max(0ll, x - 1) * max(0ll, x - 2) / 6) % mod) % mod;
    // 1 0 0
    ans = (ans + x * y * max(0ll, y - 1) / 2 % mod) % mod;
    // 1 1 0
    // 0 0 0

    return ans;
}
ll qpow(ll a, ll b) {
    ll res = 1ll;
    while (b) {
        if (b & 1)
            res = (res * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}
int main() {
    int n;
    cin >> n;
    ll res = 0;
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        for (int j = 0; j < 64; ++j) {
            if ((a[i] & (1ll << j)) != 0) {
                ++bit[j];
            }
        }
    }

    for (int j = 0; j < 64; ++j) {
        res = (res + cal(bit[j], n - bit[j]) * qpow(2, j)) % mod;
    }
    cout << res << endl;

    return 0;
}