Codeforces Round 1123 (Div. 2)
点击查看题面
A. Turn Into a Palindrome
题目大意
给定一个长度为
每次操作可以花费
求将字符串
数据范围:
- 测试用例组数
满足 。 - 字符串长度
满足 。 - 字符
及字符串 均由小写英文字母组成。
思路
根据回文字符串的定义,对于所有对称位置的下标对
- 对称位置的独立性: 对于任意一对对称位置
,对其字符的修改完全独立,不会影响其他位置的字符匹配状态。因此,可以采用双指针法,从两侧向中间逐对考察 。 - 操作特性的限制: 每次操作只能将字符修改为给定的字符
,而无法修改为其他任意字符。这意味着:
- 若要让对称位置的一对不同字符相等,其最终字符必须都变为
(因为只能向 转化)。
- 分类讨论:
- 情况 1:
该对位置天然对称,无需进行任何修改,代价为 。 - 情况 2:
必须使两端字符均变为 : - 若其中恰好有一个字符已经是
(即 或 ),则只需将另一个字符变为 ,花费 枚硬币。 - 若两者均不为
,则必须将两端的字符都变为 ,花费 枚硬币。
使用艾弗森括号(Iverson bracket)可统一表示为:
- 奇数长度中心点: 若
为奇数,中心位置字符 与自身对称,不需要进行任何变换,花费为 。
遍历所有对称对并累加代价,即为最小花费。
复杂度分析
- 时间复杂度:每个测试用例中双指针从两端向中心遍历,一共检查
对字符,单次判定与转移为 。故单组用例时间复杂度为 ,总时间复杂度为 ,在给定范围内运算量极小,可在 内轻松通过。 - 空间复杂度:仅使用常数个辅助变量维护双指针与统计和,额外空间复杂度为
(若计入存储字符串本身的空间,则为 )。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void fc() {
int n;
char c;
std::cin>>n>>c;
std::string s;
std::cin>>s;
int l=0,r=n-1;
int ans=0;
while(l<r){
if(s[l]==s[r]){
l++,r--;continue;
}
ans+=2-(s[l]==c)-(s[r]==c);
l++,r--;
}
std::cout<<ans<<"\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
std::cin>>t;
while (t--) fc();
return 0;
}B. Fashionable Array
题目大意
定义一个数组的众数(Mode)为出现次数最多的元素;若存在多个元素出现次数相同且均为最多,则取其中数值最大的元素作为众数。
现给定一个包含
输出任意一种能够达到最大和的重排方案。
数据范围:
测试用例组数
满足 。 数组大小
满足 。 数组元素
满足 。
思路
1. 众数判定机制分析
根据题目定义,当多个数值的频次并列最高时,众数选取数值最大者。这意味着在相同频次下,较大数值天然具备更高的优先级。
设当前前缀中元素的最大出现频次为
2. 分层贪心构造策略
考虑按“频次层级”将所有元素进行分轮构造:
第 1 轮:取出所有当前出现次数
的不同元素,按从大到小(降序)排列输出。 设当前各相异元素为
。 放入
后,前缀众数为 (频次为 )。 依次放入
时,所有已加入元素的频次均为 ;因 为其中的最大值,在接下来的整个区间内,每个前缀的众数均保持为 。 第
轮( ):取出所有剩余频次 的不同元素,同样按从大到小(降序)排列输出。 此时放入的元素频次均从
提升至 。 设本轮最大元素为
,它率先达到当前最高频次 ,众数立即切换为 。 随后依次填入的
虽然也相继达到最高频次 ,但均满足 。因此在该轮结束前,众数始终被最大元素 牢牢锁定。
通过这种“每一轮将所有剩余的相异元素按降序排列输出”的贪心策略,每一轮中的众数在所有可能达到的最高频次元素中均取到了理论极大值,从而保证了全局前缀众数之和最大。
复杂度分析
时间复杂度:
使用红黑树结构的
std::map统计频次并维护有序性,插入与查找复杂度为,其中 为相异元素个数。 每次逆向遍历并擦除频次归零的节点,每个元素被访问并输出
次,删除操作总计发生 次,每次删除耗时 。 单组测试用例的时间复杂度为
,在 的规模下,整体运行耗时远低于 。 空间复杂度:
std::vector<int> a存储原始输入需空间。 std::map<int, int> b存储相异元素及其频次,最多占用额外空间。 整体空间复杂度为
。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void fc() {
int n;
std::cin >> n;
std::vector<int> a(n);
std::map<int, int> b;
for (int i = 0; i < n; i++) std::cin >> a[i], b[a[i]]++;
while (b.size()) {
auto it = b.end();
it--;
while (true) {
std::cout << it->first << " ";
if (--it->second == 0) {
it = b.erase(it);
}
if (it == b.begin()) break;
--it;
}
}
std::cout << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) fc();
return 0;
}C. GCD Treasury
题目大意
海盗 Dimash 发现了一座宝库,内有
盗取过程按如下规则循环进行:
选择一个下标
( ),满足当前该堆硬币数 且 。若不存在这样的堆,则盗取终止。 令公约数
,从第 堆盗取 枚硬币( )。 将手中的数字更新为
( ),并继续循环。
求 Dimash 最多可以盗取的硬币总数量。
数据范围:
测试用例组数
满足 。 堆数
与初始数字 满足 。 硬币数
满足 。 所有测试用例的
之和满足 。
思路
1. 单堆完全盗取的不变量
设当前持有的数值为
2. 公约数整除链的传递性
设整个操作序列中依次生成的手牌数值为
因为操作终止前必须满足手牌大于 1,最终的手牌
这表明,所有被盗取的硬币堆与初始手牌
3. 质因子的包含单调性与贪心决策
若选取一个由多个质因数组合而成的因数
由于所有
因此,全局最优解必然可以在
当初始值
复杂度分析
时间复杂度:
分解质因子:试除法耗时
。 质因子遍历统计:在
范围内,相异质因子个数极少(前 7 个质数乘积 ,故相异质因子个数 )。对于每个质因子,单次线性扫描数组统计耗时 。 单组数据时间复杂度为
。总运算量不超过 ,在 2.0 秒的时限内运行效率极高。 空间复杂度:
全局静态数组存储
占用固定内存 ( )。 质因子列表
res长度不超过 6,占用常数空间。额外空间复杂度为
(计入输入数组为 )。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 3e5 + 5;
int n, g;
int a[N];
std::vector<int> f(int x) {
std::vector<int> res;
for (ll i = 2; i * i <= x; i++) {
if (x % i == 0) {
res.push_back(i);
while (x % i == 0) x /= i;
}
}
if (x > 1) res.push_back(x);
return res;
}
void fc() {
std::cin >> n >> g;
for (int i = 0; i < n; i++) std::cin >> a[i];
if (g == 1) {
std::cout << 0 << "\n";
return;
}
auto b = f(g);
ll ans = 0;
for (auto x : b) {
ll sum = 0;
for (int i = 0; i < n; i++) {
if (a[i] % x == 0) sum += a[i];
}
ans = std::max(ans, sum);
}
std::cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) fc();
return 0;
}D. Backrooms Hill
题目大意
定义一个长度为
前
个元素严格单调递增:对于所有 ,均有 ; 后
个元素严格单调递减:对于所有 ,均有 。
现给定一个由
试判断能否通过上述操作将数组
数据范围:
测试用例组数
满足 。 数组长度
满足 。 数组元素
互不相同且满足 。 所有测试用例的
之和满足 。
思路
1. 操作不变量与置换自由度分析
题目允许任意交换跨度为
- 下标奇偶性相同的两个位置可以通过相邻交换(如同冒泡排序)任意调整内部相对顺序。
- 奇数下标上的数永远无法移动到偶数下标,偶数下标上的数也永远无法移动到奇数下标。
因此,每个元素最终所在位置的下标奇偶性,必须与其初始下标的奇偶性严格一致。
2. Hill 数组的极值拓扑性质
一个严格单峰(先增后减)且元素互不相同的数组具有如下边界剥离性质:
设当前剩余元素构成的区间为
。 考察这批元素中的全局最小值:
若该最小值出现在区间内部的某个位置
( ): 若
,由严格递增性必有 ,与 是当前最小值矛盾; 若
,由严格递减性必有 ,同样产生矛盾。 由此可知:当前未安放的最小值,必须且只能放置在当前剩余目标区间的两个端点之一(即最左端
或最右端 )。
3. 双指针贪心匹配策略
基于上述性质,可采取由小到大的逆向构造法:
- 将数组
中的所有二元组 按数值升序排序。 - 维护待填充区间的左右端点双指针
,初始时 。 - 从小到大枚举每个元素,尝试将其安放在
或 :
若当前区间长度
为偶数: 端点 与 的奇偶性必然互斥(一奇一偶)。这意味着当前元素的奇偶属性至多只能与其中一个端点相吻合: 若能匹配某端点,则必须且只能放入该端点,并将对应指针内缩(
或 ); 若均无法匹配(实际在奇偶互斥时必然恰好匹配其一),则无解。
若当前区间长度
为奇数: 端点 与 具有相同的奇偶性(同奇或同偶): 若当前元素的奇偶性与两端点均不相同,则该元素既不能置于
也不能置于 ,直接判定无法构成,输出 NO;若当前元素的奇偶性与两端点相同,无论是置于左端还是右端,消耗的都是一个该奇偶性的空位,剩余未填充位置集合的奇偶配额完全对称无异。因此直接贪心放置在左端(令
)不会影响后续决策的最优性。
- 若全部
个元素均能成功就位,则说明存在合法构造,输出 YES。
复杂度分析
时间复杂度:
排序包含
个元素的 vector耗时。 双指针线性遍历并根据奇偶性判定匹配耗时
。 单组测试用例时间复杂度为
,整体时间复杂度为 。在 条件下,运行耗时约数十毫秒,远优于 2.0 秒的时限要求。 空间复杂度:
存储元素及其下标奇偶性的
std::vector<std::pair<int, int>>需要空间。 双指针及循环控制变量占用常数空间
。 整体额外空间复杂度为
。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
int n;
void fc() {
std::cin >> n;
std::vector<std::pair<int,int>>a(n);
for (int i = 0; i < n; i++) {
std::cin>>a[i].first;
a[i].second=i&1;
}
std::sort(a.begin(),a.end());
int l=0,r=n-1;
for(int i=0;i<n;i++){
if((l&1)==a[i].second)l++;
else if((r&1)==a[i].second)r--;
else {
std::cout<<"NO\n";return;
}
}
std::cout<<"YES\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) fc();
return 0;
}E. Clean Substrings
题目大意
定义一个二进制字符串 '0' 或全部由 '1' 组成)。
每次操作可以选择 '0' 变 '1','1' 变 '0'),代价为
给定一个长度为
数据范围:
组测试用例,满足 。 ,且所有测试用例的 。 - 字符串仅包含字符
'0'与'1'。
思路
1. 单个子串的美丽度分析
设子串
将同色连续段缩为一个块,整个串可以看作由
- 翻转内部的某个同色块,可以使该块与左右两相邻块合并,从而使跳变次数减少
。 - 翻转边界的同色块,可以使跳变次数减少
。 - 最终使字符串变为 clean 等价于将跳变次数减少到
。
因为每次操作至多减少
2. 上取整的奇偶拆解
由于整数上取整可表示为
从子串起点
由此可将美丽度拆解为:
3. 全局子串权值求和
对原串
两部分可以分别独立计算:
- 相邻跳变贡献之和: 考虑原串中的相邻位置
和 ( )。若 ,则该跳变落在子串 中当且仅当 且 。 合法的左端点有 个,右端点有 个。每个跳变的贡献即为 。 记所有跳变贡献之和为:
- 端点不同点对贡献之和: 考虑所有满足
且 的点对 。显然当 时 ,故必然有 。 每一个满足条件的点对 恰好对应在原串中选出一个 '0'和一个'1'。 设串中'0'的总数量为, '1'的总数量为,则满足条件的点对总数恰为:
结合两部分,整个字符串的权值即为:
4. 动态单点维护
每次单点翻转位置
- 仅会影响至多两个相邻位置的跳变状态:
和 。通过异或状态更新跳变数组 ,并相应增减 。 - 更新
与 。 - 修改和查询均可在
复杂度内完成。
复杂度分析
时间复杂度:
构建阶段:扫描字符串统计字符频数及初始相邻跳变,时间复杂度为
。 维护阶段:每次修改仅更新常数个相邻跳变与字符计数,单次查询时间复杂度为
, 次操作总耗时 。 总时间复杂度为
。 空间复杂度:
需要维护原始字符串和相邻跳变状态数组
,空间复杂度为 。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int n, q;
std::string s;
struct ST {
int m;
ll r = 0, cnt0 = 0, cnt1 = 0;
std::string t;
vector<int> b;
ST(int m, std::string& s) : m(m),t(s){
b.resize(s.size() - 1);
for (char c : s) {
c == '0' ? cnt0++ : cnt1++;
}
for (int i = 0; i < s.size() - 1; i++) {
b[i] = s[i] != s[i + 1];
if (b[i]) {
r += 1LL * (i + 1) * (m - i - 1);
}
}
print();
}
void f(int x) {
if (x < 0 || x >= m - 1) return;
if (b[x]) {
r -= 1LL * (x + 1) * (m - x - 1);
} else {
r += 1LL * (x + 1) * (m - x - 1);
}
b[x] ^= 1;
}
void modify(int x) {
f(x);
f(x - 1);
if (t[x] == '0')
cnt0--, cnt1++, t[x] = '1';
else
cnt0++, cnt1--, t[x] = '0';
print();
}
void print() { std::cout << ((r + cnt1 * cnt0) >> 1) << " "; }
};
void fc() {
std::cin >> n >> q >> s;
ST st(n, s);
while (q--) {
int x;
std::cin >> x;
st.modify(x - 1);
}
std::cout << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) fc();
return 0;
}F1. XOR Transformations (Easy Version)
题目大意
给定一个长度为
- 计算所有满足
的数对异或值 ,共有 个值; - 取其中最小的
个数值; - 用这
个数值替换原数组 。
现有
数据范围:
- 测试用例数:
- 数组大小及查询数:
, - 元素及查询操作数:
, - 保证单个测试点内
,
思路
- 状态的快速收敛性: 考虑异或运算的高位抵消性质。两两做异或时,具有相同二进制最高有效位(MSB)的数对异或后该位会变为
。变换后只保留最小的 个数,这意味着高位被消除的较小数值优先保留下来。 随着变换次数的递增,数组中相同元素的数量会急剧上升,从而产生大量的 (两个相同数异或得 )。当数组中存在 个 时,单次变换会产生 个 。一旦 (即 ),下一步变换的前 小元素将全部坍缩为 。 因此,整个数组的所有元素退化至全 所需的变换次数 极小(通常在 轮以内即可完全终止)。 - 终止态与离线预处理: 当数组最大值变为
(即升序排序后 )时,意味着数组内所有数均为 。此后无论再进行多少次变换,数组元素始终全为 ,极差恒满足 。 基于此性质,可以对每一次变换进行直接模拟,并预处理记录步数 对应的极差 。 - 回答询问: 对于每次给定的变换次数
:
- 若
,则数组已处于全 的稳态,极差直接输出 ; - 若
,直接输出预处理好的 。
复杂度分析
时间复杂度:
预处理模拟阶段:单次变换需计算
个异或值并排序,复杂度为 。由于数组元素迅速全变为 ,总变换轮数 非常小( ),因此单组数据的预处理耗时为 。在 的约束下,总运算量远低于时间上限(4.0 秒)。 查询阶段:共
次询问,每次查询通过数组下标访问达到 ,查询总复杂度为 。 总时间复杂度:
。 空间复杂度:
向量
v存储两两异或的数值,最大元素量为,占用空间为 。 答案数组及原数组大小为
。 总空间复杂度:
。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N=2005;
int n,q,a[N],ans[N];
void f(){
std::vector<int>v;v.reserve(n*n);
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
v.push_back(a[i]^a[j]);
}
}
std::sort(v.begin(),v.end());
for(int i=0;i<n;i++){
a[i]=v[i];
}
}
void fc() {
std::cin>>n>>q;
for(int i=0;i<n;i++)std::cin>>a[i];
std::sort(a,a+n);
int lim=0;
ans[lim]=a[n-1]-a[0];
while(a[n-1]!=0){
lim++;
f();
ans[lim]=a[n-1]-a[0];
}
while(q--){
int x;std::cin>>x;
std::cout<<(x>=lim?0:ans[x])<<"\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
std::cin>>t;
while (t--) fc();
return 0;
}F2. XOR Transformations (Hard Version)
题目大意
给定一个包含
计算所有满足
的数对异或值 ,共形成 个值; 取其中最小的
个数值; 用这
个数值替换原数组 。
给定
数据范围:
测试用例数:
数组大小与询问数:
, 累加和限制:
, 数值与变换次数:
, 资源限制:时间限制
,空间限制
思路
1. 稳态收敛性与极差截断
异或操作在按位运算上具有消除公共高位(最高有效位 MSB)的特性。每次变换只保留最小的
- 若数组中存在
个相同的数,它们两两异或将贡献 个 。 - 当相同数的数量增长至
(即 )时,下一次变换保留的前 个值将全部为 。 - 数组元素全部变为
所需的变换轮数 极小(通常在 轮以内即可达到)。一旦出现 ,数组达到完全稳态,后续任何轮次的极差恒为 。因此只需模拟迭代直至最大值变为 ,即可离线预处理出所有轮次的答案。
2. 无序对到有序对的等价映射
在
3. 01-Trie 结合优先队列的多路归并
将求解前
- 01-Trie 检索
小异或: 将当前数组的 个数插入一棵高度为 位的 01-Trie,并在节点上维护子树计数。利用二叉前缀树的结构,可以在 ( )的时间内查询与任意给定数值 异或所得的第 小值( 从 开始计数):
- 第
小值恒为其自身异或,结果为 ; - 第
小值即为以 为基准的最小非自反异或值。
- 小根堆多路归并: 维护一个小根优先队列
std::priority_queue,初始将每个位置的第小异或值 入堆。 每次取出堆顶的全局最小值放入结果序列 ,并将该元素的查询位序后移一位( ),在 Trie 中查询下一个第 小异或值重新插入堆中。 连续提取 个元素后,偶数索引位置 即为下一轮数组所需的精确前 小元素。
复杂度分析
时间复杂度:
单轮变换模拟:
将
个元素插入 01-Trie 耗时 ; 堆中维护
个元素的多路归并,总共提取 次,每次堆操作耗时 ,并在 Trie 中执行一次查询耗时 ,归并总耗时 ; 单轮变换复杂度为
。 全局复杂度:设收敛至全
状态的轮数为 (在给定位宽下 为很小的常数, ),预处理模拟阶段耗时为 ;回答 次询问为 查表。总时间复杂度为 ,在 限制内具有充裕的运行余量。 空间复杂度:
01-Trie 存储
个数值时最多开辟 个节点,节点状态数组空间为 ; 优先队列及辅助数组大小为
; 总空间复杂度:
,在 时空间占用约几十兆字节,满足 空间限制。
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using P = std::pair<int, int>;
const int N = 1e5 + 5;
int n, q, tim, a[N], ans[N], cnt[N];
int t[N * 30], son[N * 30][2];
void update(int x, int v = 1) {
t[v]++;
for (int i = 29; i >= 0; i--) {
int k = (x >> i) & 1;
if (!son[v][k]) son[v][k] = ++tim;
v = son[v][k];
t[v]++;
}
}
int get(int x, int k, int v = 1) {
int res = 0;
for (int i = 29; i >= 0; i--) {
int b = (x >> i) & 1;
if (t[son[v][b]] >= k) {
v = son[v][b];
res += b * (1 << i);
} else {
k -= t[son[v][b]];
v = son[v][b ^ 1];
res += (b ^ 1) * (1 << i);
}
}
return res ^ x;
}
void f() {
for (int i = 1; i <= tim; i++) son[i][0] = son[i][1] = t[i] = 0;
std::priority_queue<P, std::vector<P>, std::greater<P>> q;
tim = 1;
for (int i = 0; i < n; i++) update(a[i]), cnt[i] = 2;
for (int i = 0; i < n; i++) q.push({get(a[i], 2), i});
std::vector<int> v;
v.reserve(n << 1);
for (int _ = 1; _ <= (n << 1); _++) {
auto [val, i] = q.top();
q.pop();
v.push_back(val);
cnt[i]++;
if (cnt[i] <= n) q.push({get(a[i], cnt[i]), i});
}
for (int i = 0; i < n; i++) a[i] = v[i << 1];
}
void fc() {
std::cin >> n >> q;
for (int i = 0; i < n; i++) std::cin >> a[i];
std::sort(a, a + n);
int lim = 0;
ans[lim] = a[n - 1] - a[0];
while (a[n - 1] != 0) {
lim++;
f();
ans[lim] = a[n - 1] - a[0];
}
while (q--) {
int x;
std::cin >> x;
std::cout << (x >= lim ? 0 : ans[x]) << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) fc();
return 0;
}