NOTE比赛链接:牛客小白月赛 132
赛时四题。
A - 出题需要 rating
关键词:签到
Code
// Problem: 出题需要 rating// Contest: NowCoder// URL: https://ac.nowcoder.com/acm/contest/134527/A// Time: 2026-05-08 19:25:47
#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>;
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 m; cin >> m;
int a[7] = {0}; int n = 1000;
while (m--) { int x; cin >> x;
n += x;
if (n <= 699) a[0]++; else if (n >= 700 && n <= 1099) a[1]++; else if (n >= 1100 && n <= 1499) a[2]++; else if (n >= 1500 && n <= 1999) a[3]++; else if (n >= 2000 && n <= 2399) a[4]++; else if (n >= 2400 && n <= 2799) a[5]++; else if (n >= 2800) a[6]++; }
for (auto i: a) cout << i << " ";}
int main(){ fastio();
int T = 1; // cin >> T;
while (T--) solve();
return 0;}B - 出题需要语文
关键词:模拟
思路
我们需要维护三个信息:题目编号、题目难度、题目质量,可以使用结构体存储并重载小于号。
但赛时的思维有点混乱,选择了 map<char, vector<pii>>,赛后复盘时发现结构体更好一点。
Code
// Problem: 出题需要语文// Contest: NowCoder// URL: https://ac.nowcoder.com/acm/contest/134527/B// Time: 2026-05-08 19:30:56#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>;
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 = 1e5 + 10;
map<char, vector<pii>> mp;
bool cmp(pii &A, pii &B){ return A.second > B.second;}
void solve(){ int n; cin >> n;
for (int i = 1; i <= n; i++) { char c; int q; cin >> c >> q;
mp[c].push_back({i, q}); }
if (mp.size() < 6) { cout << -1 << endl; return; }
vector<pii> res;
for (auto &[c, vec]: mp) { sort(all(vec), cmp); res.push_back(vec[0]); }
double ave = 0; for (auto i: res) { ave += i.second; if (i.second < 60) { cout << -1 << endl; return; } }
ave = ave * 1.0 / 6; // cout << ave << endl;
if (ave >= 70) for (auto i: res) cout << i.first << " "; else cout << -1 << endl;}
int main(){ fastio();
int T = 1; // cin >> T;
while (T--) solve();
return 0;}C - 出题需要加法
关键词:位运算,二分
思路
刚开始的时候想到一个思路:使用 bitset 来考察区间 [L, R] 上每个数的二进制表示,只有某个数的二进制表示中 1 的个数 才计入答案,但这样做在区间跨度很大的情况下会超时,并且没有特判 0 和 1 的情况。
正确的思路是,我们直接构造出所有符合条件的 w, 和 的取值范围最大只有 (因为 ),我们可以使用两层循环嵌套枚举 和 ,计算所有可能的 。符合条件的数大约只有 2000 个,把这些数去重并排序,对于每一次查询,使用二分查找即可。
NOTE
distance(itl, itr)函数可以计算两个迭代器之间的距离,它支持所有合法迭代器,但不会检查非法性。这道题注意使用
unsigned long long。
Code
// Problem: 出题需要加法// Contest: NowCoder// URL: https://ac.nowcoder.com/acm/contest/134527/C// Time: 2026-05-08 19:43:51#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>;
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;
vector<ull> nums;
void init(){ for (int i = 0; i < 63; i++) for (int j = 0; j < 63; j++) { ull tmp = (1ull << i) + (1ull << j); nums.push_back(tmp); }
sort(all(nums)); nums.erase(unique(all(nums)), nums.end());}
void solve(){ ll l, r; cin >> l >> r;
auto itL = lower_bound(all(nums), l); auto itR = upper_bound(all(nums), r);
cout << distance(itL, itR) << endl;}
int main(){ fastio();
int T = 1; cin >> T;
init();
while (T--) solve();
return 0;}D - 出题需要构造
关键词:构造
思路
NOTE赛时竟然想出来了这题的思路,感觉自己进步了。下面抛开直觉,更严谨地考虑这个问题。
我们需要在 的矩阵中填入 的颜色,使得任意选出 种颜色,矩阵的每一行和每一列都恰好有 2 个格子被点亮。
Step 1:确定 的值
单独看矩阵的某一列(长度为 )。假设在这列中,颜色 出现了 次。题目要求:任意选 种颜色,这 种颜色出现的次数总和必定为 2。
即对于任意大小为 的集合 ,都有 。既然对任意组合都成立,这就意味着所有颜色的出现次数必须完全相同。
设每个颜色在这一列出现 次,那么就有:,因为 和 都是正整数,满足这个等式的组合只有两种:
我们把这两种情况代入列的总长度(这一列共有 个格子):
- 如果 :意味着 种颜色,每种颜色在这列出现了 次。那么这一列的总格子数应该是 。但这列的长度是 (),矛盾
- 如果 :意味着每种颜色在这列恰好出现 次。那么总格子数是 。完全合理!
结论 1: 必定等于 。且每种颜色在每一列中恰好出现 1 次。
Step 2:确定 和 的关系
我们用同样的逻辑去分析某一行(长度为 )。
为了满足任意 2 种颜色在这一行恰好点亮 2 个格子,那么每种颜色在这一行中也必须恰好出现 1 次。
既然 种颜色每种都在这一行出现 1 次,那么这一行的长度就必须是 。
但这一行的长度是 ,所以:
结论 2:如果 ,绝对不可能存在合法方案,直接输出 NO。
另外,因为需要选出 种颜色,所以颜色总数 必须 (如果 也是 NO)。
Step3:构造方案
现在问题转化成了:构造一个 的方阵,用 填满,使得每行每列的 都恰好出现一次。
直接循环移位即可。使用 rotate() 会方便一些。
Code
// Problem: 出题需要构造// Contest: NowCoder// URL: https://ac.nowcoder.com/acm/contest/134527/D// Time: 2026-05-08 20:01:20#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>;
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 || n == 1) { cout << "NO" << endl; return; } else { cout << "YES" << endl << 2 << endl; vector<int> a(n); iota(all(a), 1);
for (int i = 0; i < m; i++) { for (auto x: a) cout << x << " "; cout << endl;
rotate(a.begin(), a.begin() + 1, a.end()); } }}
int main(){ fastio();
int T = 1; cin >> T;
while (T--) solve();
return 0;}E - 出题需要魔法
关键词:单调栈,补集计数
思路
对于排列中的某一个元素 ,题目要求找包含 的子区间 (即 ),使得 是“可见的”。
“可见的”意味着满足以下两个条件之一:
- 左边可见:区间 中, 是最大值。也就是说,不能有比 大的数出现在 左边。
- 右边可见:区间 中, 是最大值。也就是说,不能有比 大的数出现在 右边。
为了快速判断,我们可以预处理出两个数组:
- :表示在 左边,第一个大于 的元素的下标。如果不存在,则 。
- :表示在 右边,第一个大于 的元素的下标。如果不存在,则 。
NOTE这部分预处理使用单调栈,从两侧分别扫一遍即可。
单调栈的作用就是找左右的第一个大于或者小于的坐标,一定要记住。
接下来我们把条件转化为对区间端点 和 的限制条件:
-
满足“左边可见”的方案数 ():
要保证左边没有比 大的数,左端点 必须越过 ,即 。右端点 可以随便选,即 。
所以,
-
满足“右边可见”的方案数 ():
要保证右边没有比 大的数,右端点 必须在 之前,即 。左端点 可以随便选,即 。
所以,
-
同时满足“左边和右边都可见”的方案数 ():
取上面两个条件的交集,即 且 。
所以,
根据容斥原理,满足“至少一边可见”的总方案数就是:
Code
// Problem: 出题需要魔法// Contest: NowCoder// URL: https://ac.nowcoder.com/acm/contest/134527/E// Time: 2026-05-08 20:37:27#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>;
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<int> a(n + 1); for (int i = 1; i <= n; i++) cin >> a[i];
vector<int> L(n + 1, 0); vector<int> R(n + 1, n + 1);
stack<int> st; for (int i = 1; i <= n; i++) { while (!st.empty() && a[st.top()] < a[i]) st.pop();
if (!st.empty()) L[i] = st.top();
st.push(i); }
stack<int> st2; for (int i = n; i >= 1; i--) { while (!st2.empty() && a[st2.top()] < a[i]) st2.pop();
if (!st2.empty()) R[i] = st2.top();
st2.push(i); }
for (int i = 1; i <= n; i++) { ll W1 = 1LL * (i - L[i]) * (n - i + 1); ll W2 = 1LL * i * (R[i] - i); ll W12 = 1LL * (i - L[i]) * (R[i] - i);
ll res = W1 + W2 - W12;
cout << res << " "; } cout << "\n";}
int main(){ fastio();
int T = 1; cin >> T;
while (T--) solve();
return 0;}F - 出题需要树论
关键词:树形DP,前后缀最大值
思路
Gemini 对话记录,这个题有点困难,现在还不能完全理解。
假设我们选择节点 进行操作,将其子树内所有节点的点权 变为 。考虑这个操作对树上从根节点到任意节点 的路径和造成的影响。
我们考虑把所有节点分为两类:
-
不在 的子树内:
此时,从根节点 到 的路径完全不会经过 的子树,所以路径和保持不变,依然是原本的权值和。
-
在 的子树内:
此时,从根节点 到 的路径被分成了两段:
- :这一段不在 的子树内,权值保持不变。
- :这一段全部在 的子树内,所有的点权都被异或了 。
为了快速计算,我们可以用 DFS 预处理出两个数组:
- :表示原树中,从根 到节点 的原点权路径和。
- :表示假如所有节点点权都被异或 (即 ),从根 到节点 的新点权路径和。
如果选择了节点 进行操作,那么对于子树内的任意节点 ,新的路径和可以表示为:
其中 对于固定的 是一个常数。因此,子树内的最大路径和为:。
而对于子树外的节点,最大路径和就是:
NOTE如何快速求“子树内/外”的最大值?
- 子树内的 最大值可以通过一次后序遍历计算;
- 子树外的 最大值,我们可以利用 DFS 序将树压缩成一维数组。子树外就对应了数组里的一段前缀和一段后缀。我们可以预处理该数组的前缀最大值 (pref) 和 后缀最大值 (suff),做到 查询。
DFS 序可以将一棵树拉直成一个一维数组,并且保证任何一棵子树在这个数组里都是连续的一段。
通过维护一个时间戳
timer,我们记录进树时间dfn[u] = timer,记录进一维数组seq[timer] = u后递归子树,递归完成后记录出树时间out[u] = timer。我们可以将 [进树时间,出树时间] 看作一个区间,去一维数组里找这个子树。
Code
// Problem: 出题需要树论// Contest: NowCoder// URL: https://ac.nowcoder.com/acm/contest/134527/F// Time: 2026-05-08 22:45:58#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>;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};const ll inf = 1e18;const int N = 0;
void solve(){ int n; ll x; cin >> n >> x;
vector<ll> w(n + 1); for (int i = 1; i <= n; i++) cin >> w[i];
vector<vector<int>> adj(n + 1); for (int i = 1; i < n; i++) { int u, v; cin >> u >> v; adj[u].push_back(v), adj[v].push_back(u); }
// A[i] 存储原权值路径和,B[i] 存储异或后权值路径和 vector<ll> A(n + 1, 0), B(n + 1, 0); vector<int> dfn(n + 1), out(n + 1), seq(n + 1), par(n + 1, 0); vector<ll> maxB(n + 1, 0); int t = 0;
auto dfs = [&](auto &self, int u, int p) -> void { par[u] = p; A[u] = A[p] + w[u]; B[u] = B[p] + (w[u] ^ x);
t++; dfn[u] = t; seq[t] = u;
maxB[u] = B[u];
for (int v: adj[u]) { if (v != p) { self(self, v, u); maxB[u] = max(maxB[u], maxB[v]); } } out[u] = t; };
dfs(dfs, 1, 0);
vector<ll> prefA(n + 2, 0), suffA(n + 2, 0); for (int i = 1; i <= n; i++) prefA[i] = max(prefA[i - 1], A[seq[i]]);
for (int i = n; i >= 1; i--) suffA[i] = max(suffA[i + 1], A[seq[i]]);
ll res = inf;
for (int u = 1; u <= n; u++) { int p = par[u];
// 子树内部的最大路径和 ll I = A[p] - B[p] + maxB[u];
// 子树外部的最大路径和 ll O = max(prefA[dfn[u] - 1], suffA[out[u] + 1]);
ll tmp = max(I, O); res = min(res, tmp); }
cout << res << "\n";}
int main(){ fastio();
int T = 1; cin >> T;
while (T--) solve();
return 0;}