2024 牛客多校 1
0. preface
https://ac.nowcoder.com/acm/contest/81596
过题数
,几乎可补题。除非是高科技题。 ,酌情可补题。可能对得上技能树。 ,几乎不可补题。除非是一些低科技的神秘启发题。
本场共
简单数学题 基础数学题 + 动态规划 简单注意力题/数学题 正常题 多个高科技嵌套题。不可补。 模拟 + DP 题 + 可被单调栈优化的转移。有启发意义。 高科技题。不可补。 简单思维题 tarjan + 模拟。 中级科技题嵌套 + 启发题 + 工业题。精神好的时候可补。 神秘图论题。不可补。
1. H
https://ac.nowcoder.com/acm/contest/81596/H
题意
有两场 World Finals 同时举办,每场都有若干出线队伍,两场出线名单可能有重复的队伍。一个队伍只能选择其中的一场参加,然后每场比赛的每支队伍都有一个预测成绩(过题数 + 罚时)。
lzr010506 在两场 World Finals 都出线了,他想知道如果所有队伍的最终成绩就是预测成绩,并且可以任意分配两场都出线了队伍的比赛场次的选择,他们队最高可能的排名是多少。
保证不会出现同题同罚时。
题解
注意到比赛场数是常数。考虑
考虑任意一场比赛,既然只要求高排名,不妨让所有获得两场名额的队伍都假设为参加另一场。
实现
想想就感觉 STL 搞来搞去很烦。题目钦定了顺序不妨用结构体顺便重载顺序。
字符串不需要离散化,不触发字符串的比较符可以保证复杂度。
首先就是一个离线。为了记每个队伍在哪场出现过,一个状压(常数)。
然后继续遍历筛选只在这场会出现的或
然后排序。暴力找一下位置就行了。
时间反正是
手速狗的养成……
Code
struct Team { std::string name; int nums, times; Team () {} Team (std::string name_, int nums_, int times_) { name = name_; nums = nums_; times = times_; } bool operator < (const Team& o) const { if (nums != o.nums) { return nums > o.nums; } else { return times < o.times; } } }; void solve() { std::map<std::string, int> occur; int n; std::cin >> n; std::vector<Team> vec1(n + 1); for (int i = 1; i <= n; i++) { std::string s; int nums; int times; std::cin >> s >> nums >> times; vec1[i] = {s, nums, times}; occur[s] |= 1 << 0; } int m; std::cin >> m; std::vector<Team> vec2(m + 1); for (int i = 1; i <= m; i++) { std::string s; int nums; int times; std::cin >> s >> nums >> times; vec2[i] = {s, nums, times}; occur[s] |= 1 << 1; } const std::string t = "lzr010506"; std::vector<Team> vec3; for (int i = 1; i <= n; i++) { std::string s = vec1[i].name; int nums = vec1[i].nums; int times = vec1[i].times; if (s == t || (occur[s] >> 0 & 1 && ~ (occur[s] >> 1) & 1)) { vec3.push_back(vec1[i]); } } std::sort(vec3.begin(), vec3.end()); const int INF = 1 << 30; int ans = INF; for (int i = 0; i < vec3.size(); i++) { // std::cout << vec3[i].name << " " << vec3[i].nums << " " << vec3[i].times << "\n"; if (vec3[i].name == t) { ans = std::min(i + 1, ans); break; } } vec3.clear(); for (int i = 1; i <= m; i++) { std::string s = vec2[i].name; int nums = vec2[i].nums; int times = vec2[i].times; if (s == t || (occur[s] >> 1 & 1 && ~ (occur[s] >> 0) & 1)) { vec3.push_back(vec2[i]); } } std::sort(vec3.begin(), vec3.end()); for (int i = 0; i < vec3.size(); i++) { if (vec3[i].name == t) { ans = std::min(i + 1, ans); break; } } std::cout << ans << "\n"; }
如果问题变为排名比率最高怎么办?不妨让当所有前一场的两场名额都有的比
2. C
https://ac.nowcoder.com/acm/contest/81596/C
题意
维护一个初始为空的非负整数序列,支持
每次操作移除末尾的
每次操作后输出当前序列所有后缀和的总和。
答案对
题解
考虑一个序列
考虑维护一个前缀数组
时间复杂度
Code
const int MOD = 1000000007; void solve() { int n; std::cin >> n; i64 cost = 0; std::vector<i64> a; for (int i = 1; i <= n; i++) { int x, v; std::cin >> x >> v; for (int j = 0; j < x; j++) { if (a.size()) { cost = (cost - a.back() * a.size() % MOD + MOD) % MOD; a.pop_back(); } } a.push_back(v); cost = (cost + a.back() * a.size() % MOD) % MOD; std::cout << cost << "\n"; } }
3. A
先说教育意义,数和数位分离考虑的好题。
题意
求有多少长为
题解
首先想到
第一个思路尝试
问题可以想成。对二进制下的低
这样想的代价是什么?首先要把
第二个思路尝试
换一种思路。问题可以想成。钦定一个序列的
既然这样,于是选
考虑一个经典的思路:按位考虑。
- 最低位是
的数,一定不会贡献给 和是 的子序列。- 于是这
个数的前 个位置可以任意选,每一位上有 种选择。所有位的方案数按乘法原理是 。
- 于是这
- 最低位是
的数,只需存在一个子序列满足 和是 ,即存在一个子序列的前 位的 和是 。- 正难则反,不难考虑到 “存在一个子序列的前
位的 和是 ” 等于 “样本空间” - “子序列的前 为的 和恒为 ” 。 - 于是前
个位置,每个位置有 种选择。所有位的方案数按乘法原理是 。
- 正难则反,不难考虑到 “存在一个子序列的前
于是存在这种一个子序列的
面对
有时候注意一下组合数的范围不一定是和
预处理组合数
Code
int n, m, q; std::cin >> n >> m >> q; std::vector<std::vector<i64> > C(n + 1, std::vector<i64>(n + 1)); C[0][0] = 1; for (int i = 1; i <= n; i++) { C[i][0] = C[i][i] = 1; for (int j = 1; j < i; j++) { C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % q; } } auto ksm = [&] (i64 a, i64 n) -> i64 { i64 res = 1; a %= q; for (;n;n>>=1,a=a*a%q)if(n&1)res=res*a%q; return res; }; i64 ans = 0; for (i64 k = 1; k <= n; k++) { i64 P = (C[n][k] * ksm(2, 1LL * (n - k) * (m - 1))) % q; i64 Q = (ksm(ksm(2, k) - 1, m - 1) + q) % q; ans = (ans + P * Q % q) % q; } std::cout << ans << "\n";
4. B
题意
求有多少长为
题解
敲黑板,仔细分析。这题典型处理挺多。
考点一:
首先动动脑子上点智力。存在两个不好考虑,但是可以正难则反地容斥。即
首先,存在多少长为
然后,存在多少长为
考点二:
考虑钦定一个序列的
考点三:
考虑最低位是
- 首先如果这
个数存在一个非空真子序列满足 和是 ,则这个非空真子序列的超序列也满足 和是 。于是这 个数就是这个子序列。 - 问题变成了,这
个数的前 位,每位的与和都是 ,且任意删掉一个数会导致与和变为 。
分析这个新问题。考虑某一位,一定存在两个很强的限制:
- 如果不存在
则一定导致这一位的与和为 。 - 如果存在
个 ,则删去任意一个数,这位上的与和依旧为 。
考虑某位上存在且仅存在
考点四:
称一个特殊位对应一个数,当且仅当这个数被删除时,改特殊位上的
- 当
,前 位上存在有 个特殊位和 个最低位为 的数一一对应。 - 当
,不在乎特殊位,只需要前 位上全是 。
考点五:
设
联想到斯特林数的递推,每个特殊位相当于一个球,每个数相当于一个非空箱子。
考虑当前有
- 这个特殊位上的
可以属于到这 个数的一个。考虑属于哪个数,共有 种可能。于是 。 - 这个特殊为上的
可以不属于这 个数的一个。考虑新增一个数的位置在哪,原来 个数贡献了 个空位,于是有 种可能。于是 。
进行一个归纳得到
考虑初始化,
当钦定了
于是存在且仅存在一个子序列的答案根据乘法原理为
归纳出存在两个子序列的答案为
计算答案的时间复杂度为
考点六:
最后,这 byd 题目卡非常量的取模速度了。
两种优化之一可以通过:
- 优化掉快速幂:少一个
。 - 使用动态模数:模动态模时速度
,模静态为负优化。
优化掉快速幂
int n, m, q; std::cin >> n >> m >> q; const int N = std::max(n, m); std::vector<std::vector<i64> > C(N + 1, std::vector<i64>(N + 1)); C[0][0] = 1; for (int i = 1; i <= N; i++) { C[i][0] = C[i][i] = 1; for (int j = 1; j < i; j++) { C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % q; } } std::vector<std::vector<i64> > dp(n + 1, std::vector<i64>(m + 1)); dp[0][0] = 1; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { dp[i][j] = (i * ((dp[i - 1][j - 1] + dp[i][j - 1]) % q)) % q; } } std::vector<int> pw2(N + 1); pw2[0] = 1; for (int i = 1; i <= N; i++) { pw2[i] = (pw2[i - 1] << 1) % q; } std::vector<i64> f(n + 1), g(n + 1); i64 ans = 0; for (i64 k = 1; k <= n; k++) { if (k == 1) { g[k] = 1; } f[k] = C[n][k]; i64 P = 1; // ^{0} i64 H = 1; // ^{0} for (int j = m - 1; j >= 1; --j) { f[k] = (f[k] * pw2[n - k]) % q; P = (P * (pw2[k] - 1 + q)) % q; if (k > 1 && j >= k) { i64 Q = (C[m - 1][j] * dp[k][j]) % q; g[k] = (g[k] + Q * H % q) % q; } H = (H * (pw2[k] - 1 - k + q)) % q; } ans = (ans + f[k] * (P - g[k]) % q) % q; } ans = (ans + q) % q; std::cout << ans << "\n";
不优化快速幂但是动态取模
struct DynamicModInt { // 动态模加速 i128 base = 1; int mod; void init(int mod_){ mod = mod_; base = (base << 64) / mod; } i64 operator () (i64 x) { // 自动取非负数 i64 res = x - mod * (base * x >> 64); return res == mod ? 0 : res; } } mol; void solve() { i64 n, m, q; std::cin >> n >> m >> q; mol.init(q); const int N = std::max(n, m); std::vector<std::vector<i64> > C(N + 1, std::vector<i64>(N + 1)); C[0][0] = 1; for (int i = 1; i <= N; i++) { C[i][0] = C[i][i] = 1; for (int j = 1; j < i; j++) { C[i][j] = mol(C[i - 1][j - 1] + C[i - 1][j]); } } std::vector<int> pw2(N * N + 1); pw2[0] = 1; for (int i = 1; i <= N * N; i++) { pw2[i] = mol(pw2[i - 1] << 1); } auto ksm = [&] (i64 a, i64 n) -> i64 { i64 res = 1; a = mol(a + q); for (;n;n>>=1,a=mol(a*a))if(n&1)res=mol(res*a); return res; }; i64 ans = 0; for (i64 k = 1; k <= n; k++) { i64 P = mol(C[n][k] * ksm(2, 1LL * (n - k) * (m - 1))); i64 Q = mol(ksm(ksm(2, k) - 1, m - 1)); ans = mol(ans + P * Q); } std::vector<std::vector<i64> > dp(n + 1, std::vector<i64>(m + 1)); dp[0][0] = 1; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { dp[i][j] = mol( i64(i) * mol(dp[i - 1][j - 1] + dp[i][j - 1]) ); } } std::vector<i64> f(n + 1), g(n + 1); for (i64 k = 1; k <= n; k++) { f[k] = mol(C[n][k] * pw2[(n - k) * (m - 1)]); if (k == 1) { g[k] = 1; } else { for (i64 t = k; t <= m - 1; t++) { i64 Q = mol(C[m - 1][t] * dp[k][t]); i64 H = ksm(pw2[k] - 1 - k, m - 1 - t); g[k] = mol(g[k] + mol(Q * H)); } } ans = mol(ans - mol(f[k] * g[k])); } ans = mol(ans + q); std::cout << ans << "\n"; }
5. I
题意
有一个
有
题解
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】