Codeforces 2217
Codeforces Round 1091 (Div. 2) and CodeCraft 26
A. The Equalizer

如果本来的总数是奇数那就能赢,否则就花一步把总数变成 \(nk\),然后轮到别人,所以 \(nk\) 需要为偶数。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k;
const int N=10005;
int a[N];
inline void solve(){
cin>>n>>k;
int sum=0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
}
if((sum&1)||!((n*k)&1)){
cout<<"YES\n";
}
else{
cout<<"NO\n";
}
return;
}
signed main(void){
cin.tie(NULL)->sync_with_stdio(false);
int T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
/*
5
4 3 2 5 1
*/
B. Flip the Bit (Easy Version)

左右两边分别翻转取最大值,注意最后检查特殊索引处反转前后是否相等
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k;
const int N=2e5+5;
int a[N];
int p;
inline void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
cin>>p;
int tar=a[p];
int zuo=0,you=0,cnt=0;
for(int i=n;i>p;i--){
int tmp=a[i];
if(cnt){
tmp^=1;
}
if(tmp!=tar){
you++;
cnt^=1;
}
else{
continue;
}
}
if(cnt){
you++;
}
int cnt2=0;
for(int i=1;i<p;i++){
int tmp=a[i];
if(cnt2){
tmp^=1;
}
if(tmp!=tar){
zuo++;
cnt2^=1;
}
else{
continue;
}
}
if(cnt2){
zuo++;
}
cout<<max(zuo,you)<<'\n';
// cout<<zuo<<" "<<you<<endl;
return;
}
signed main(void){
cin.tie(NULL)->sync_with_stdio(false);
int T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
/*
5
4 3 2 5 1
*/
C. Grid Covering

向右跳和向下跳需要可以遍历到所有格子,即 \(\text{gcd}(n,a)=1\) 和 \(\text{gcd}
(m,b)=1\),个完整的移动周期(1 次向下移动,1 次向右移动)产生的位移为 \((+a,+b)\)。要以完全相同的下一步回到 \((A1,1)\) ,我们需要 \(p\) 个循环,其中:
\(p⋅a≡0(modn),p⋅b≡0(modm)\)这样 p的最小值是 \(lcm(n,m)\)。因此,在 \(2⋅lcm(n,m)\) 步之后,棋路就完全循环了。
如果 \(gcd(n,m)=1\),此处为 \(2⋅lcm(n,m)=2⋅n⋅m\)。循环长度是网格瓦片数的两倍。根据对称性,每个方格都会被访问两次(每个方向一次)。因此,网格是完全覆盖的。
如果 \(gcd(n,m)=2\),此处为 \(2⋅lcm(n,m)=n⋅m\)。这样是循环走两轮可以覆盖整个网格。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,a,b;
const int N=2e5+5;
inline void solve(){
cin>>n>>m>>a>>b;
// cerr<<__gcd(n,a)<<" "<<__gcd(m,b)<<" "<<__gcd(n,m)<<'\n';
if(__gcd(n,a)==1&&__gcd(m,b)==1&&2*n*m/__gcd(n,m)>=n*m){
cout<<"YES\n";
}
else{
cout<<"NO\n";
}
return;
}
signed main(void){
cin.tie(NULL)->sync_with_stdio(false);
int T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
/*
5
4 3 2 5 1
*/
D. Flip the Bit (Hard Version)

核心解题思路
-
转化为全 0 数组
定义目标值为特殊索引处的初始值 \(x = a[p_1]\)。如果我们将数组 \(a\) 中的每个元素与 \(x\) 进行异或操作,可以得到一个新数组 \(b_i = a_i \oplus x\)。此时,我们的目标就变成了:用最少的操作将数组 \(b\) 变成全 0 数组。在这个新数组中,所有特殊索引的初始值必定已经是 0。 -
引入差分数组降维
对于任何区间翻转问题,标准的技巧是使用差分数组。我们在 \(b\) 数组前后各补一个 0(即 \(b_0 = 0\) 且 \(b_{n+1} = 0\)),然后构造差分数组 \(c_i = b_i \oplus b_{i-1}\)。
每次对范围 \([l, r]\) 进行位翻转,在差分数组上的效果仅仅是翻转了 \(c_l\) 和 \(c_{r+1}\) 两个点的值。
因此,我们的目标是消除差分数组 \(c\) 中所有的 1(由于首尾为 0,\(c\) 中 1 的数量必定是偶数)。每次操作相当于从中选取两个为 1 的索引 \(u\) 和 \(v\),将它们一起变为 0。 -
操作的限制与代价
题目要求每次翻转的区间 \([l, r]\) 必须至少包含一个特殊索引 \(p_i\)。- 代价为 1 的配对:如果两个需要消除的索引 \(u\) 和 \(v\)(\(u < v\))跨越了至少一个特殊索引(即存在 \(p_i\) 使得 \(u \le p_i < v\)),那么我们直接翻转区间 \([u, v-1]\) 即可。由于区间包含了 \(p_i\),这完全符合题意,只需 1 次操作。
- 代价为 2 的配对:如果 \(u\) 和 \(v\) 之间没有任何特殊索引,我们无法直接消除它们。但我们可以借用一个跨越了特殊索引的“跳板”位置 \(Z\) 分别与 \(u\) 和 \(v\) 操作。因为 \(Z\) 被翻转了两次,其状态保持不变,而 \(u\) 和 \(v\) 被成功消除,这需要 2 次操作。
-
多分图匹配(贪心推导)
我们将特殊索引 \(p_1, p_2, \ldots, p_k\) 视为边界,它们将整个差分数组 \(c\) 划分成了 \(k+1\) 个独立“区域”(Region):- 区域 0:索引 \(\le p_1\)
- 区域 \(j\):索引 \(> p_j\) 且 \(\le p_{j+1}\)
- 区域 \(k\):索引 \(> p_k\)
规律十分明朗:同区域内的 1 互相配对需要 2 次操作,跨区域的 1 互相配对只需 1 次操作。
设 \(S\) 为 \(c\) 数组中 1 的总数量,设 \(L\) 为所有区域中包含 1 最多的那个区域的 1 的数量。
- 如果 \(L \le S/2\),说明没有任何一个区域的 1 过度集中,所有的 1 都可以实现跨区域完美配对!此时总代价为 \(S/2\)。
- 如果 \(L > S/2\),意味着最大的那个区域中有部分 1 找不到外部的 1 来配对,只能被迫在区域内部消耗(代价为 2)。多出来的局部配对使得最小代价恰好变为 \(L\)。
综上,最少操作次数的公式极其简洁:\(\max(S/2, L)\)。
C++ 完整代码
这个算法的时间复杂度为 \(O(n)\),空间复杂度也为 \(O(n)\),可以极其高效地通过 \(2 \cdot 10^5\) 级别的数据约束。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void solve() {
int n, k;
cin >> n >> k;
vector<int> a(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
vector<int> p(k);
for (int i = 0; i < k; ++i) {
cin >> p[i];
}
// 目标值 x
int x = a[p[0]];
// 构造 b 数组,使得目标转化为把 b 全变为 0
vector<int> b(n + 2, 0);
for (int i = 1; i <= n; ++i) {
b[i] = a[i] ^ x;
}
// 构造差分数组 c,并统计所有 1 的总数 (S_size)
vector<int> c(n + 2, 0);
int S_size = 0;
for (int i = 1; i <= n + 1; ++i) {
c[i] = b[i] ^ b[i - 1];
if (c[i] == 1) {
S_size++;
}
}
// 统计每个区域内 1 的个数
// p_idx 用于追踪当前正在参考的特殊索引边界
vector<int> C(k + 1, 0);
int p_idx = 0;
for (int i = 1; i <= n + 1; ++i) {
if (c[i] == 1) {
// 如果当前索引 i 越过了当前的特殊索引,则进入下一个区域
while (p_idx < k && i > p[p_idx]) {
p_idx++;
}
C[p_idx]++;
}
}
// 寻找包含最多 1 的区域,求出其数量 L
int L = 0;
for (int i = 0; i <= k; ++i) {
L = max(L, C[i]);
}
// 结论公式:全局的一半与局部最大值的最大者
cout << max(S_size / 2, L) << "\n";
}
int main() {
// 优化输入输出速度
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int t;
if (cin >> t) {
while (t--) {
solve();
}
}
return 0;
}

浙公网安备 33010602011771号