牛客小白月赛#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);
}

浙公网安备 33010602011771号