牛客小白月赛#25 题解

这一场的小白月赛对萌新真的友好,连我这个蒟蒻都能切\(6\)题。(赛后发现有并查集的题又能切一题)。主要是本场要涉及的算法也不算很多,卡我的似乎都是数论的题😔。

因为本场有几道分数结果要取\(1e9+7\)模,如果不太懂,可以戳这的A题。(快速幂+费马大定理求逆元)

J. 异或和之和 #组合公式 #逆元 #位运算

题目链接

题意

给定含有\(n\)个正整数的数组,现要你从该数中任取\(3\)个元素进行异或运算后再总的求和的值。即,从数组中取一共\(C^3_n\)个三元组\(\{ a,b,c \}\),先计算这些三元组内部异或(\(a\oplus b\oplus c\)),再对所有异或结果进行求和,注意要对\(1e9+7\)取模。

分析

对于位运算后求其和,需要用到按位统计求贡献的思想。也就说,我们枚举每一数位,讨论在当前特定数位\(i\)下,三三整数进行组合,找到某个三元组在第\(i\)位下异或结果能够为\(1\)的(说明对总和结果贡献\(+1\)),统计有多少个贡献值为\(1\)的三元组,乘上该特定数位\(i\)下对应的\(2^i\)(权重),实际上就是对总和的加成。

我们可以发现,三元内部某一相同的数位如果异或和为\(1\),这一数位下的情况,要么是\(1\oplus1\oplus1\),要么\(0\oplus0\oplus1\)。也就说,我们如果能找到某个三元组合在某个位下是\(1\ 1\ 1\)或者\(0\ 0\ 1\)组合(\(0\ 0\ 1\)组合与\(0\ 1\ 0\)组合是等价的,因为异或满足交换律),对总和的贡献值就为\(1\)了。

接下来,我们统计,数组中有多少整数在第\(i\)数位下为\(1\),记录为\(count[i]\)。那么能够组合\(1\ 1\ 1\)的组合数为\(C^3_{count[i]}\),能够组合\(0\ 0\ 1\)的组合数为\(C^2_{n - count[i]} \times count[i]\),总的组合数为\(C^3_{count[i]}+C^2_{n - count[i]} \times count[i]\),那么第\(i\)位,对最终求和结果的贡献值即为\(2^i\times (C^3_{count[i]}+C^2_{n - count[i]} \times count[i])\)。枚举所有\(i\),对贡献值求和即可。

之所以先把\(J\)题放前面,是为了引入组合公式写法(\(B\)题要用到)。

#include <string>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <map>
#include <vector>
#include <deque>
#include <algorithm>
#include <set>
#include <unordered_map>
using namespace std;
using ll = long long;
using ld = long double;
const int MAXN = 1e5 + 5;
const int MOD = 1e9 + 7;
const double EPS = 1e-8;
int n, cnt[65];
ll ans;
ll quickPower(ll base, ll power) {
    ll ans = 1;
    while (power > 0) {
        if (power & 1) ans = ans * base % MOD;
        power >>= 1;
        base = (base * base) % MOD;
    }
    return ans;
}
ll Inv(ll x) { //逆元求分母在取模意义下的值
    return quickPower(x, MOD - 2);
}
ll C(ll x, ll y) { //组合公式(取模意义下)
    if (x < y) return 0;
    ll res = 1;
    for (ll i = 0; i < y; i++) {
        res = res * (x - i) % MOD; //分子
        res = res * Inv(i + 1) % MOD;  //分母
    }
    return res;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        int j = 0; ll x;
        scanf("%lld", &x);
        while (x) {
            cnt[j++] += (x & 1);
            x >>= 1;
        }
    }
    for (int i = 0; i < 64; i++) {
        ll C_3 = C(cnt[i], 3), C_2 = C(n - cnt[i], 2);
        ans = ans + (1LL << i) % MOD * (C_3 + C_2 * (ll)cnt[i] % MOD) % MOD;
        ans = ans % MOD;
    }
    printf("%lld\n", ans % MOD);
    return 0;
}

B. k-size字符串 #组合公式 #逆元

题目链接

题意

\(k-size\)字符定义为,字符串恰好存在\(k\)个连续段(相同连续字母组成的子串)。现给定\(n\)个字母\(a\)字符,\(m\)个字母\(b\)字符,组成长度为\(n+m\)\(k-size\)字符串,问有多少种组成方式(对\(1e9+7\)取模)

分析

先特判下条件,当\(n+m<k\)时,即要求的连续段超过了字符串长度,无法组合,答案为\(0\);当\(k=1\)时,由于题目给定至少一个\(a\)和至少一个\(b\),至少能组成组成\(2\)个连续段,无法满足\(k=1\),答案也为\(0\)

为了方便地分隔出连续段,我们不妨先取\(k\)字符,排成这样的排列(即\(a,b\)字母相互交错):\(ababab...\)。此时的串不但长度为\(k\),同时也满足存在\(k\)个连续段。那么还有剩余的字母\(a,b\)呢?(简而言之,就是采用挡板法的思想,但是有些小细节要注意下)

  • \(k\)为奇数的时候,此时的串,

    如果第一个字符排的是\(a\),最后一个字符一定也是\(a\),即\(ababab...aba\);此时我已将\(\lfloor k/2\rfloor+1\)\(a\)\(\lfloor k/2\rfloor\)\(b\)排到上述串中了。现在需要将剩下的\(n-(\lfloor k/2\rfloor+1)\)\(a\)(即有\(n-(\lfloor k/2\rfloor+1-1)\)个挡板插入空位)插到原串中的每个\(a\)附近(即有\(\lfloor k/2\rfloor+1\)个挡板),允许对原串中某个\(a\)不插入剩下的\(a\)(即允许空盒)。联想到组合数学中的挡板法(见下面的补充,注意这里是非空盒子),对于字符\(a\)的方案数为\(C_{n-1}^{\lfloor k/2 \rfloor + 1 - 1}\)。此外,还需要将剩下的\(n-(\lfloor k/2\rfloor)\)\(b\)插到原串中每个\(b\)(共\(\lfloor k/2\rfloor\)个)附近,和上面同理,对于字符\(b\)的方案数为\(C_{m-1}^{\lfloor k/2 \rfloor - 1}\)。因此由乘法原理,\(ababab...aba\)方案数为\(C_{n-1}^{\lfloor k/2 \rfloor + 1 - 1} \times C_{m-1}^{\lfloor k/2 \rfloor - 1}\)

    [关于挡板法]:有相同的\(n\)个球,要将所有球插到\(m\)个不同盒子且每个盒子一定要“非空”,相当于有\(m-1\)个挡板,插入到\(n-1\)个可插入空隙,组合数共有\(C_{n-1}^{m-1}\)

    然而,如果每个盒子不一定非空呢?我们可以先从外部给每个盒子分别借来一个球(从而保证每个盒子此时已经有一个盒子,进而可以使用上面的挡板法了),即总共借来了\(m\)个球,现总共有\(n+m\)个球(\(n+m-1\)个可插入空隙),还是\(m-1\)个挡板,组合数为\(C^{m-1}_{n+m-1}\)

    同理,如果如果第一个字符排的是\(b\),最后一个字符一定也是\(b\),即\(bababa...bab\);此时我已将\(\lfloor k/2\rfloor\)\(a\)\(\lfloor k/2\rfloor+1\)\(b\)排到上述串中了。按照上面的方法,类比得到组合数为\(C_{n-1}^{\lfloor k/2 \rfloor - 1} \times C_{m-1}^{\lfloor k/2 \rfloor + 1 - 1}\)

    综上,由加法原理,当\(k\)为奇数的时候,组合数有\(C_{n-1}^{\lfloor k/2 \rfloor + 1 - 1} \times C_{m-1}^{\lfloor k/2 \rfloor - 1}+C_{n-1}^{\lfloor k/2 \rfloor - 1} \times C_{m-1}^{\lfloor k/2 \rfloor + 1 - 1}\)

  • \(k\)为偶数时,如果第一个字符排的是\(a\),最后一个字符一定也是\(b\),即\(ababab...ab\),方法还是采用上面的,方案数为\(C_{n-1}^{k/2- 1} \times C_{m-1}^{k/2- 1}\);如果第一个字符排的是\(b\),最后一个字符一定也是\(a\),即\(bababa...ba\),方法还是采用上面的,方案数一样是为\(C_{n-1}^{k/2- 1} \times C_{m-1}^{k/2- 1}\)

    综上,当\(k\)为奇数的时候,组合数有\(2\times C_{n-1}^{k/2- 1} \times C_{m-1}^{k/2- 1}\)

#include <string>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <map>
#include <vector>
#include <deque>
#include <algorithm>
#include <set>
#include <unordered_map>
using namespace std;
using ll = long long;
const int MOD = 1e9 + 7;
ll ans;
int n, m, k;
ll quickPower(ll base, ll power) {
    ll ans = 1;
    while (power > 0) {
        if (power & 1) ans = ans * base % MOD;
        power >>= 1;
        base = (base * base) % MOD;
    }
    return ans;
}
ll Inv(ll x) { //逆元求分母在取模意义下的值
    return quickPower(x, MOD - 2);
}
ll C(ll x, ll y) { //组合公式(取模意义下)
    if (x < y) return 0;
    ll res = 1;
    for (ll i = 0; i < y; i++) {
        res = res * (x - i) % MOD; //分子
        res = res * Inv(i + 1) % MOD;  //分母
    }
    return res;
}
int main() {
    scanf("%d%d%d", &n, &m, &k);
    if (n + m < k || k == 1)
        ans = 0;
    else if (k & 1)
        ans = (C(n - 1, k / 2 + 1 - 1) * C(m - 1, k / 2 - 1    ) % MOD 
             + C(n - 1, k / 2 - 1    ) * C(m - 1, k / 2 + 1 - 1) % MOD) % MOD;
    else
        ans = 2 * C(n - 1, k / 2 - 1) * C(m - 1, k / 2 - 1) % MOD;
    printf("%lld\n", ans);
    return 0;   
}

C. 白魔法师

题目链接

题意

树上有\(n(n\leq1e5)\)个点,每个点为黑色或白色。你可选定一个点,将其染为白色。你要保证染完后整颗树当中最大的白色连通块(该连通子图上所有点均为白色)尽可能大,现要你求出最大白色连通块大小。

分析

先利用题目给定的边,将白色点合并为连通块,合并完后统计每个连通块的规模大小。然后枚举每一个黑色点的邻接点所在连通块的总大小,更新最值即可。

所有点是白色点的情况,需要特判一下,答案为\(1\)赛场上我忘记特判,直接白给

#include <string>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <map>
#include <vector>
#include <deque>
#include <algorithm>
#include <set>
#include <unordered_map>
using namespace std;
using ll = long long;
const int MAXN = 1e5 + 5;
const int MOD = 1e9 + 7;
int fa[MAXN], n, sz[MAXN], cnt = 0;
vector<int> H[MAXN];
bool isWhite[MAXN];
string str;
int findSet(int x) {
    if (x != fa[x]) {
        fa[x] = findSet(fa[x]);
    }
    return fa[x];
}
int main() {
    scanf("%d", &n);
    cin >> str;
    for (int i = 0; i < str.length(); i++) {
        isWhite[i + 1] = (str[i] == 'W');
        fa[i + 1] = i + 1;
    }
    for (int i = 1, u, v; i <= n - 1; i++) {
        scanf("%d%d", &u, &v);
        H[u].push_back(v);
        H[v].push_back(u);
        if (isWhite[u] && isWhite[v]) {
            int pu = findSet(u), pv = findSet(v);
            fa[pu] = pv;
        }
    }
    for (int i = 1; i <= n; i++) findSet(i);
    for (int i = 1; i <= n; i++) 
        if (isWhite[i]) {
            sz[fa[i]]++; cnt++;
        }
    int most = 0;
    if (cnt == n) most = n;
    else {
        for (int i = 1; i <= n; i++) {
            if (isWhite[i]) continue;
            int sum = 1;
            for (int j = 0; j < H[i].size(); j++) {
                int v = H[i][j]; if (!isWhite[v]) continue;
                sum += sz[fa[v]];
            }
            most = max(most, sum);
        }
    }
    printf("%d\n", most);
}

G. 解方程 #二分思想

题目链接

题意

解方程\(x^a+b\ln x=c\),其中\(1\leq a\leq 3,1\leq b,c\leq 1e9\),解出的\(x\)误差不超过\(1e-7\)

分析

考虑到\(a,b\)数据范围下界,左边等式是个单调增加的函数。又因为\(c\)下界大于\(0\),当\(x=1\)时,左边等式\(\leq c\)。故二分时初始下界\(lo = 1\)。显然上界为\(1e9\)

这里要注意下,由官方题解,\(double\)的精度不够高,后面二分时\(r-l\)无限不动,导致\(TLE\)应换用\(long\ double\)类型。

#include <string>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <map>
#include <vector>
#include <deque>
#include <algorithm>
#include <set>
#include <unordered_map>
using namespace std;
using ll = long long;
using ld = long double;
const int MAXN = 1e5 + 5;
const int MOD = 1e9 + 7;
const double EPS = 1e-8;
int a, b, c;
ld calculate(ld x) {
    ld rev = 1;
    for (int i = 1; i <= a; i++) rev *= x;
    rev += b * log(x);
    return rev;
}
int main() {
    scanf("%d%d%d", &a, &b, &c);
    ld lo = 1, hi = 1e9 + 5;
    while (hi - lo > EPS) {
        ld mid = (ld)(lo + hi) / 2;
        if (calculate(mid) - (ld)c >= 0) hi = mid;
        else lo = mid;
    }
    printf("%.7Lf\n", lo);
}
posted @ 2020-10-06 11:40  J_StrawHat  阅读(210)  评论(0)    收藏  举报