2026 ICPC Asia EC 网络赛(2)
J. Joker or Judger
题目大意
给定评测人员收到的
WrongProblemX:分 SameProblemX:分 UnreasonableProblemArrangement:分 UnreasonableLimitForProblemX:分 WeakTestsForProblemX:分 BadProblemX:分
其中
试计算所有评价的总扣分,若严格大于 Joker;否则输出 Judger。
数据范围:
, - 评价字符串
满足 ,且仅由大小写英文字母组成 - 保证单个测试用例中所有字符串数量之和
思路
本题属于规则明确的字符串匹配与模拟统计问题。
- 规则分类:
- 无后缀固定串:只有
"UnreasonableProblemArrangement"一种,若完全相等则直接计分。 - 带后缀变量串:其余所有合法规则均以单个大写字母
结尾。
- 匹配逻辑:
- 读入评价字符串
。 - 若
,则得分增加 。 - 否则,检验其末尾字符
是否在区间 内: - 若在区间内,将末尾字符弹出,得到前缀串
; - 将
在预设的哈希映射表(包含 WrongProblem、SameProblem、UnreasonableLimitForProblem、WeakTestsForProblem、BadProblem及其对应分值)中进行查找并累加分值; - 若查找不存在或末尾字符不合规,则分值为
。
- 阈值判定:
- 遍历完
个字符串后,比较累计总分 与阈值 。若 输出 Joker,否则输出Judger。
复杂度分析
- 时间复杂度:每个测试用例处理
个长度不超过 的字符串。单次前缀截取与哈希匹配开销为 。单测试点总时间复杂度为 。在 与 的约束下,计算量约为 次基本操作,运行耗时在毫秒级。 - 空间复杂度:哈希映射仅存储固定数量的模式串,占用常数空间
;字符串操作所需辅助空间为 。因此整体辅助空间复杂度为 。
参考代码
参考代码
#include <iostream>
#include <string>
#include <unordered_map>
void solve() {
int n, p;
std::cin >> n >> p;
// 预先建立带参负面评价的前缀与对应权重的映射
static const std::unordered_map<std::string, int> score_table = {
{"WrongProblem", 100},
{"SameProblem", 30},
{"UnreasonableLimitForProblem", 5},
{"WeakTestsForProblem", 3},
{"BadProblem", 1}
};
long long total_score = 0;
for (int i = 0; i < n; ++i) {
std::string s;
std::cin >> s;
// 判定无后缀固定规则
if (s == "UnreasonableProblemArrangement") {
total_score += 10;
} else if (!s.empty()) {
char last_char = s.back();
// 校验末尾占位符 X 是否为合法题号 ('A' ~ 'L')
if (last_char >= 'A' && last_char <= 'L') {
s.pop_back(); // 移除占位字符以提取模式前缀
auto it = score_table.find(s);
if (it != score_table.end()) {
total_score += it->second;
}
}
}
}
// 判断扣分是否超出预设阈值
if (total_score > p) {
std::cout << "Joker\n";
} else {
std::cout << "Judger\n";
}
}
int main() {
// 提高 I/O 交互效率
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}L. Loop
题目大意
给定一个长度为
现有一旅行者从左上角
数据范围:
- 保证单个测试用例中所有数据规模之和
思路
- 格点权值的代数化简: 根据转移式
,沿主对角线方向移动时权值保持不变,归纳可得:
即格点
- 状态映射与游走模型: 从
行走至 共包含 次“向下”移动与 次“向右”移动。 令坐标差值 :
- 向下移动一步:
; - 向右移动一步:
。
起点处
由于
- 若
,则 ,格点权值为 ; - 若
,则 ,格点权值为 。
若构造辅助数组
- 最优游走策略与贪心极值: 原问题等价于在一维链图上寻找一条由
步 与 步 构成的回路,且起止点均为 。 由于所有权值非负,且步数奇偶交替,任意多余步数的消耗均等价于在某一相邻点对 之间往返振荡(每次往返消耗一个 和一个 ,代价为两点权值和)。 为了最小化总代价,最优路径必然选择完全在非负半轴(序列 )或非正半轴(序列 )内进行,并将多余步数全部集中于代价最小的相邻层之间: 以正半轴(序列 )为例,设选取的振荡区间最高层为 与 (其中 ):
- 从
直达 ,点 在进出过程中各经过 次,贡献 ; - 剩余
对往返步数全部用于在 与 之间来回振荡,使得点 共计被访问 次,点 共计被访问 次。
合并各项系数,该振荡方案对应的总权值为:
同理,负半轴(序列
- 算法流程: 特殊判断
时答案即为 。 对于 ,预处理序列 和 的前缀和,线性枚举分界层 并取所有候选值的全局最小值即可。
复杂度分析
- 时间复杂度:构造序列
及两序列的前缀和耗时 ;遍历枚举振荡分界点 耗时 。单测试用例运行时间为 ,在 的限制下总时间复杂度为 ,可在毫秒级内完成。 - 空间复杂度:需要辅助数组存储反向移位序列
及其前缀和,空间复杂度为 。
参考代码
参考代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using ll = long long;
void solve() {
int n;
std::cin >> n;
std::vector<ll> a(n), b(n);
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
// 边界特判:起点即终点
if (n == 1) {
std::cout << a[0] << "\n";
return;
}
// 构造负半轴偏移映射对应的序列 b
b[0] = a[0];
for (int i = 1; i < n; ++i) {
b[i] = a[n - i];
}
// 预计算序列 a 与序列 b 的前缀和
std::vector<ll> pre_a(n + 1, 0), pre_b(n + 1, 0);
for (int i = 0; i < n; ++i) {
pre_a[i + 1] = pre_a[i] + a[i];
pre_b[i + 1] = pre_b[i] + b[i];
}
ll min_cost = LLONG_MAX;
// 枚举在相邻层 (i, i + 1) 进行步数消解振荡的决策点
for (int i = 0; i < n - 1; ++i) {
// 在负半轴 (b 序列) 振荡的总代价
ll cost_b = (b[i] + b[i + 1]) * (n - i) - b[i + 1] + pre_b[i] * 2;
// 在正半轴 (a 序列) 振荡的总代价
ll cost_a = (a[i] + a[i + 1]) * (n - i) - a[i + 1] + pre_a[i] * 2;
min_cost = std::min({min_cost, cost_a, cost_b});
}
std::cout << min_cost << "\n";
}
int main() {
// 提高 I/O 执行效率
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}K. K-MEX
题目大意
给定一个长度为
定义一次
共有
数据范围:
- 保证单个测试点内
,
思路
- 基准
与非平凡 的判定:
- 设原序列的
。根据定义,数 已经在原序列中存在,而 在原序列中不存在。 - 若某种变换能够使得新的
严格大于 ,则新序列中必须构造出数值 。 - 由于原序列中不存在
,数值 只能由某个元素 经过变换得到,即满足:
- 关键结论:若给定的询问参数
,则任何元素都无法通过变换生成 。这意味着无论如何选择,新序列中必定缺失 ,因此必有 。 - 由于原序列中本质不同的元素至多有
个,因此可能使得答案大于 的“关键 ”至多只有 种。其余绝大多数询问的答案直接为 。
- 配对覆盖分析:
固定一个关键候选值
。我们需要从 开始依次向上检验 是否能被同时覆盖进新序列中。 对于任意数值
,其变换后的互补伙伴为 。 原序列中的
可以保持为 或变为 ; 原序列中的
可以保持为 或变为 ; 其它任何数值均无法转化为
或 。 因此,无序数对
在序列中所拥有的可用总资源数为 。 考察从小到大贪心验证当前目标
能否存在的过程: 情况一:
或 : 若 ,原序列中无负数,无法通过补数提供贡献;若 (即 ),变换前后数值不变。两者均要求原序列中必须原本就存在 ,即需满足 。 情况二:
: 由于 ,说明 尚未在当前之前的检验中被强制要求占用。此时只需保证 能够被表示即可,条件为 。 情况三:
: 由于 ,说明 在此前已被作为必须出现的数字占用。为同时保留 并构造出 ,无序对 必须提供至少两个独立元素,条件为 。 一旦上述某一判定条件被打破,说明当前
无法被构造,贪心立即终止,此时的最大可能 即为当前的 。
- 算法流程:
- 统计频数并计算初始
。 - 提取原序列中去重后的所有数值
,生成至多 个候选 。 - 对每个候选
,从 开始递增模拟检验直到失败,将二元组 记录在哈希表或有序数组中。 - 处理
次询问:在记录中进行检索。若检索命中则累加(异或)其预处理结果;否则直接异或基准值 。
复杂度分析
时间复杂度:
计算初始
与离散化去重开销为 。 关键参数
至多有 个。对于每个 , 的枚举上限为 (因为序列长度为 , 不可能超过 )。借助有序表对频数进行二分查找耗时 。因此单次测试预处理时间为 ,在 的数据规模下运算量约为 级别,运行极快。 处理
次询问,单次二分检索耗时 ,总开销为 。 总体时间复杂度为
。 空间复杂度:
仅需保存去重后的元素、频数表以及至多
个候选二元组,空间复杂度为 。
参考代码
参考代码
#include <iostream>
#include <vector>
#include <algorithm>
using ll = long long;
const int N = 5e3 + 5;
int n;
int a[N], cnt[N], vals[N];
// 计算原序列的基础 mex
int get_mex() {
int mex = 0;
while (cnt[mex]) mex++;
return mex;
}
void solve() {
std::cin >> n;
// 清空当前测试用例中可能用到的频数数组
for (int i = 0; i <= n + 2; i++) {
cnt[i] = 0;
}
for (int i = 0; i < n; i++) {
std::cin >> a[i];
vals[i] = a[i];
if (a[i] <= n + 2) {
cnt[a[i]]++;
}
}
int mex = get_mex();
int ans = 0;
// 对原数组进行排序与去重,便于后续统计与二分
std::sort(vals, vals + n);
std::vector<std::pair<int, int>> cc;
for (int i = 0; i < n;) {
int j = i;
while (i < n && vals[i] == vals[j]) i++;
cc.push_back({vals[j], i - j});
}
// 快速获取任意数值 val 在原序列中的出现频数
auto get_c = [&](int val) -> int {
if (val < 0) return 0;
if (val <= n + 2) return cnt[val];
auto it = std::lower_bound(cc.begin(), cc.end(), std::make_pair(val, 0));
if (it != cc.end() && it->first == val) return it->second;
return 0;
};
// 预处理所有可能产生 mex 提升的关键 k
std::vector<std::pair<int, int>> as;
as.reserve(cc.size());
for (auto& [x, c] : cc) {
int k = mex + x;
int nmex = mex;
// 从 mex 开始逐步递增校验连续性
while (nmex < n) {
int u = k - nmex;
int cu = get_c(u);
int cm = cnt[nmex];
// 依据 u 与 nmex 的相对大小与有效性进行资源充足性判定
if (u < 0 || u == nmex) {
if (cm < 1) break;
} else if (u > nmex) {
if (cu + cm < 1) break;
} else if (u < nmex) {
if (cu + cm < 2) break;
}
nmex++;
}
as.push_back({k, nmex});
}
// 保证查询序列按 k 升序排列
std::sort(as.begin(), as.end());
int q;
std::cin >> q;
while (q--) {
int k;
std::cin >> k;
// 若 k 命中候选集合则取强化后的 mex,否则结果为基准 mex
auto it = std::lower_bound(as.begin(), as.end(), std::make_pair(k, 0));
if (it != as.end() && it->first == k) {
ans ^= it->second;
} else {
ans ^= mex;
}
}
std::cout << ans << '\n';
}
int main() {
// 优化 I/O 效率以应对较大规模的查询数据
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}