2342 字
12 分钟
牛客周赛 Round 144
NOTE

比赛链接:牛客周赛 Round 144

A - 我是谁?#

关键词:签到

思路#

统计四个字母出现次数,判断是否一致即可。

Code#

// Problem: 我是谁?
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/134957/A
// Time: 2026-05-17 19:00:06
#include <bits/stdc++.h>
using namespace std;
// clang-format off
#define endl '\n'
#define all(x) (x).begin(), (x).end()
#define fastio() ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
// clang-format on
using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;
using pdd = pair<double, double>;
using pll = pair<long long, long long>;
using i128 = __int128;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 0;
int a, b, cc, d;
void solve()
{
int n;
string s;
cin >> n >> s;
for (auto c: s)
{
if (c == 'A')
a++;
else if (c == 'B')
b++;
else if (c == 'C')
cc++;
else
d++;
}
int ave = (a + b + cc + d) / 4;
if (a == ave && b == ave && cc == ave && d == ave)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
int main()
{
fastio();
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}

B - 我是清楚姐姐#

关键词:构造

思路#

显然只有 1,2,31, 2, 3 可以构造出来合法的矩阵。

Code#

// Problem: 我是清楚姐姐
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/134957/B
// Time: 2026-05-17 19:03:22
#include <bits/stdc++.h>
using namespace std;
// clang-format off
#define endl '\n'
#define all(x) (x).begin(), (x).end()
#define fastio() ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
// clang-format on
using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;
using pdd = pair<double, double>;
using pll = pair<long long, long long>;
using i128 = __int128;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 0;
void solve()
{
int n;
cin >> n;
if (n == 2)
{
cout << 1 << " " << 2 << endl;
cout << 3 << " " << 4 << endl;
return;
}
if (n == 1)
{
cout << 1 << endl;
return;
}
if (n == 3)
{
cout << 1 << " " << 2 << " " << 3 << endl;
cout << 5 << " " << 4 << " " << 6 << endl;
cout << 7 << " " << 8 << " " << 9 << endl;
return;
}
cout << -1 << endl;
}
int main()
{
fastio();
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}

C - 其实我是小苯#

关键词:构造

思路#

由于给定长度过大,我们考虑直接构造字符串。

n=mn = m 时(两数长度相同)

  • 最小值:因为长度相同,我们完全可以让 a=ba = b,这样 ab|a - b| 的最小值为 0
  • 最大值:我们要让其中一个数尽可能大,另一个尽可能小。
    • 最大数是 nn 个 9:99...99
    • 最小数是 1 后面跟 n1n-1 个 0:10...00
    • 两者相减:999100=89999\dots9 - 10\dots0 = \mathbf{89\dots9}(即 1 个 8 后面跟着 n1n-19)。

n>mn > m 时(两数长度不同)

此时必然有 a>ba > b,所以 ab=ab|a - b| = a - b

  • 最小值:我们要让 aa 尽可能小,bb 尽可能大。
    • 最小的 a=10n1a = 10^{n-1}(1 后面 n1n-1 个 0)
    • 最大的 b=10m1b = 10^m - 1mm 个 9)
    • 两者相减:10n110m+110^{n-1} - 10^m + 1
    • 如何化为字符串?
      • 如果 n=m+1n = m + 1(例如 n=3,m=2n=3, m=2),10099=1100 - 99 = \mathbf{1}
      • 如果 n>m+1n > m + 1(例如 n=5,m=2n=5, m=2),1000099=990110000 - 99 = \mathbf{9901}
      • 规律:连续 nm1n - m - 19,然后 m1m - 10,最后 1
  • 最大值:我们要让 aa 尽可能大,bb 尽可能小。
    • 最大的 a=10n1a = 10^n - 1nn 个 9)
    • 最小的 b=10m1b = 10^{m-1}(1 后面 m1m-1 个 0)
    • 两者相减:10n10m1110^n - 10^{m-1} - 1
    • 例如 n=4,m=2n=4, m=2 时:999910=99899999 - 10 = \mathbf{9989}
    • 规律: nmn - m9,然后 1 个 8,最后 m1m - 19

Code#

// Problem: 其实我是小苯
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/134957/C
// Time: 2026-05-17 19:09:59
#include <bits/stdc++.h>
using namespace std;
// clang-format off
#define endl '\n'
#define all(x) (x).begin(), (x).end()
#define fastio() ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
// clang-format on
using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;
using pdd = pair<double, double>;
using pll = pair<long long, long long>;
using i128 = __int128;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 0;
void solve()
{
int n, m;
cin >> n >> m;
if (n > m)
swap(n, m);
string mn = "";
if (n == m)
mn = "0";
else if (m == n + 1)
mn = "1";
else
mn = string(m - n - 1, '9') + string(n - 1, '0') + "1";
string mx = string(m - n, '9') + "8" + string(n - 1, '9');
cout << mn << " " << mx << endl;
}
int main()
{
fastio();
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}

D - 骗你的,我是小红#

关键词:位运算,组合数学

思路#

题目要求找满足 ixorji \operatorname{xor} jkk 的倍数的数对 (i,j)(i, j)。如果一个数 XXkk 的倍数,意味着在二进制表示下,XX 的最低 pp 位必须全是 0。

而一个数的最低 pp 位,在数学上是这个数对 2p2^p 取模的余数(也就是对 kk 取模的余数)。

TIP

要理解这个概念,可以从十进制来理解。

一个数的最低两位(个位和十位),其实就是这个数除以 102=10010^2=100 的余数。

推广到一般进制,对于一个用基数 bb 表示的数 NN,它的最低 pp 位,就是 NN 除以 bpb^p 的余数。

原问题就可以转化为:在区间 [l,r][l, r] 内,有多少对 (i,j)(i, j) 满足它们除以 kk 的余数相同?

也就是说,我们需要统计区间 [l,r][l, r] 内,模 kk 余数为 0,1,2,,k10, 1, 2, \dots, k-1 的数字分别有多少个。假设余数为 mm 的数字有 CmC_m 个,那么从这些数字中任选 2 个组成对的方案数就是 Cm×(Cm1)2\frac{C_m \times (C_m - 1)}{2}。把所有余数的方案数加起来就是最终答案。

我们可以用整除分块的思想直接 O(1)O(1) 算出来:

  1. 区间 [l,r][l, r] 的总长度是 len=rl+1len = r - l + 1
  2. 每连续的 kk 个数,必然完美地覆盖了模 kk 的所有余数(00k1k-1 各出现一次)。
  3. 整个区间可以分为 len / k 个完整的块,以及末尾剩下的一小截,长度为 len % k
  4. 这意味着:
    • len % k 种余数,它们多出现了一次,总共出现了 len / k + 1 次。
    • 剩下的 k - len % k 种余数,出现了 len / k 次。

Code#

// Problem: 骗你的,其实我是小红
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/134957/D
// Time: 2026-05-17 19:24:28
#include <bits/stdc++.h>
using namespace std;
// clang-format off
#define endl '\n'
#define all(x) (x).begin(), (x).end()
#define fastio() ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
// clang-format on
using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;
using pdd = pair<double, double>;
using pll = pair<long long, long long>;
using i128 = __int128;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 0;
void solve()
{
ll l, r, k;
cin >> l >> r >> k;
ll len = r - l + 1;
ll cnt = len / k;
ll rem = len % k;
cout << (k - rem) * (cnt * (cnt - 1) / 2) + rem * (cnt * (cnt + 1) / 2) << endl;
}
int main()
{
fastio();
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}

E - 好吧,我是Bingbong#

关键词:DFS

思路#

注水的过程本质上是一个由管道高度决定的 DFS 顺序,对于同一个瓶子的所有子树,管道位置越低,越先被完全灌满。一个节点 uu 的瓶子达到满水时,uu 的整棵子树也已经全部注满。

Ans[i]=sum_subtree[i]+dist[i]Ans[i] = \text{sum\_subtree}[i] + \text{dist}[i]

  • sum_subtree[i]\text{sum\_subtree}[i]:表示以 ii 为根的整棵子树,全部注满水所需要的总水量。
  • dist[i]\text{dist}[i]:表示为了让水能够刚好流到节点 ii 的“入水口”,它的所有祖先节点以及旁支子树所截留的总历史耗水

对于 dist[i],可以这样理解:因为水必须填满这个节点的父亲节点,才可能流下来到子节点,因此这些被提前消耗的水对于节点 ii 来说就是外部的。

Code#

// Problem: 好吧,我是Bingbong
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/134957/E
// Time: 2026-05-17 20:35:50
#include <bits/stdc++.h>
using namespace std;
// clang-format off
#define endl '\n'
#define all(x) (x).begin(), (x).end()
#define fastio() ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
// clang-format on
using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;
using pdd = pair<double, double>;
using pll = pair<long long, long long>;
using i128 = __int128;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 0;
void solve()
{
int n;
cin >> n;
vector<ll> h(n + 1), f(n + 1), w(n + 1);
for (int i = 1; i <= n; i++)
cin >> h[i];
for (int i = 2; i <= n; i++)
cin >> f[i];
vector<vector<pll>> g(n + 1);
for (int i = 2; i <= n; i++)
{
cin >> w[i];
g[f[i]].push_back({w[i], i}); // 管道高度-子节点编号
}
vector<ll> sum(n + 1), pre(n + 1), res(n + 1);
// 计算节点u及其整棵子树,如果全部注满水需要多少水量
auto dfs1 = [&](auto &&self, int u) -> void
{
sum[u] = h[u];
for (auto [ht, v]: g[u])
{
self(self, v);
sum[u] += sum[v];
}
};
dfs1(dfs1, 1);
// 代表水位上升,水一定会先流进位置低的管道
for (int i = 1; i <= n; i++)
sort(all(g[i]));
auto dfs2 = [&](auto &&self, int u) -> void
{
res[u] = pre[u] + sum[u];
ll cur = 0;
// cur表示当前水位下,所有旁支子树的总容量
for (auto [ht, v]: g[u])
{
// 算 v 的截留代价 = 祖先截留(pre[u]) + 自身高度(ht) + 比它低的旁支容量(cur)
pre[v] = pre[u] + ht + cur;
// 算完v以后需要把v的整棵子树容量加入cur,因为对于下一个更高的管子来说,v必须要被填满
cur += sum[v];
self(self, v);
}
};
dfs2(dfs2, 1);
for (int i = 1; i <= n; i++)
cout << res[i] << " ";
}
int main()
{
fastio();
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}