2592 字
13 分钟
cf edu round 190
NOTE

比赛链接:Educational Codeforces Round 190 (Rated for Div.2)

A - Optimal Purchase#

关键词:模拟

思路#

考虑三种情况:

  1. 为所有的学生买个人秘钥
  2. n / 3 组学生买团体秘钥,并为 n % 3 个学生购买个人秘钥
  3. n / 3 组学生买团体秘钥,并为 n % 3 个学生购买团体秘钥

取最小值即可。

Code#

// Problem: CF 2230 A
// Contest: Codeforces - Educational Codeforces Round 190 (Rated for Div. 2)
// URL: https://codeforces.com/contest/2230/problem/A
// Time: 2026-05-18 23:03:28
#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>;
using i128 = __int128;
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()
{
ll n, a, b;
cin >> n >> a >> b;
ll res1 = a * n;
ll res2 = b * (n / 3) + min(a * (n % 3), b);
cout << min(res1, res2) << endl;
}
int main()
{
fastio();
int T = 1;
cin >> T;
while (T--)
solve();
return 0;
}

B - Digit String#

关键词:枚举,前缀和

思路#

注意到所有的 44 都不能选,因为总可以选择 44 本身作为构成 44 的倍数的元素。

考虑哪些组合可以是 44 的倍数,只有如下几个:12,32,24,4412, 32, 24, 44,而含有 44 的又不可以被选,因此只需要考虑 12,3212, 32

不能出现 12,3212, 32 的子序列,意味着所有的 1133 都必须排在 22 的后面。

因此最终保留的合法字符一定是 2222若干个 213113若干个 1 和 3\underbrace{22\dots22}_{\text{若干个 } 2} \quad \underbrace{13113\dots}_{\text{若干个 } 1 \text{ 和 } 3} 的形式。

换句话说,整个合法的字符串一定可以被一条“分割线”切成两半:左半边只能包含 22,右半边:只能包含 1133

使用前缀和维护 1,2,31, 2, 3 的出现次数,枚举分割点,并计算最多能保留下来的字符数。

Code#

// Problem: CF 2230 B
// Contest: Codeforces - Educational Codeforces Round 190 (Rated for Div. 2)
// URL: https://codeforces.com/contest/2230/problem/B
// Time: 2026-05-18 23:10:18
#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>;
using i128 = __int128;
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()
{
string s;
cin >> s;
int n = s.size();
vector<int> a(n), b(n), c(n);
for (int i = 0; i < n; i++)
{
if (s[i] == '1')
a[i]++;
else if (s[i] == '2')
b[i]++;
else if (s[i] == '3')
c[i]++;
if (i == 0)
continue;
a[i] = a[i] + a[i - 1];
b[i] = b[i] + b[i - 1];
c[i] = c[i] + c[i - 1];
}
int res = 0;
for (int i = 0; i <= n; i++)
{
int l2 = (i == 0) ? 0 : b[i - 1];
int r1 = a[n - 1] - ((i == 0) ? 0 : a[i - 1]);
int r3 = c[n - 1] - ((i == 0) ? 0 : c[i - 1]);
res = max(res, l2 + r1 + r3);
}
cout << n - res << endl;
}
int main()
{
fastio();
int T = 1;
cin >> T;
while (T--)
solve();
return 0;
}

C - Arrange the Numbers in a Circle#

关键词:思维

思路#

每一组连续的三张卡片中至少有两个是相同的,意味着不能出现连续三张牌是完全不同的数字

从这个角度出发,我们考虑将所有的牌分成两类:

  1. 基础牌:数量 2\ge 2 的数字。它们能自己组成连续的相同牌(如 A,AA, A),因此是构建圆环的基础。

  2. 孤儿牌:数量为 11 的数字。它们孤立存在,容易与两边的数字形成“三张不同”的非法组合。

并且我们发现,只要孤儿牌出现在圆环中,它的两侧一定是同一种基础牌,如 A,S,A\dots A, S, A \dots,而不能是 A,S,BA, S, B

既然孤儿牌需要基础牌的“庇护”,那我们考察基础牌能提供多少庇护容量。这里分三种情况:

  1. 完全没有基础牌:答案直接为 00
  2. 只有 1 种基础牌 (假设该数字有 CC 张),每隔绝一个孤儿就需要左右两侧各有一个 AA,所以 kk 个孤儿需要 2k2kAA。 容纳上限为:C/2\lfloor C / 2 \rfloor
  3. 2\ge 2 种基础牌 (比如 A,B,CA, B, C\dots),为了保证基础牌之间不发生违规,我们让每种基础牌各自抱团,形如 AA,BB,CCA \dots A, B \dots B, C \dots C。 在这个大的圆环里,基础牌 AA 除了要保护内部的孤儿,它的左边界和右边界还要去和别的字母交接。 为了防止边界出现违规,基础牌 AA 必须留出“一头一尾”各 1 张牌去当屏障。所以,只有剩下的 C2C - 2 张牌可以起到庇护作用。 此时,基础牌 AA 能容纳的孤儿数量上限变成了:(C2)/2\lfloor (C - 2) / 2 \rfloor

Code#

// Problem: CF 2230 C
// Contest: Codeforces - Educational Codeforces Round 190 (Rated for Div. 2)
// URL: https://codeforces.com/contest/2230/problem/C
// Time: 2026-05-18 23:28:41
#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>;
using i128 = __int128;
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;
ll base = 0, single = 0, capacity = 0, sum = 0;
for (int i = 0; i < n; i++)
{
ll x;
cin >> x;
if (x == 1)
single++;
// 收集所有数量>=2的牌作为基础牌并计算容量
else
{
base++;
sum += x;
capacity += (x - 2) / 2;
}
}
ll res = 0;
if (base == 0) // 没有基础牌
res = 0;
else if (base == 1) // 只有一种基础牌
res = sum + min(single, sum / 2);
else // 有两种以上的基础牌
res = sum + min(single, capacity);
if (res < 3)
cout << 0 << endl;
else
cout << res << endl;
}
int main()
{
fastio();
int T = 1;
cin >> T;
while (T--)
solve();
return 0;
}

D - Good Schedule#

关键词:DP

思路#

两个人追剧必须是按顺序来的,也就是开始的目标集都是第一集,如果电视放的是目标集就看,看完后目标集变成下一集,否则他们会无视。我们需要找一个区间 [L,R][L, R],在这个区间里,要么一起看同一集,要么一起啥也不看。这就意味着,他们在整个区间里,目标集永远是同步的

假设我们固定了区间的起点 LL,初始时两人都需要看第 1 集。

接下来会发生什么?我们寻找第 1 集在 aabb首次出现的日子:设 Alice 首次遇到第 1 集的日子是 posAposA,设 Bob 首次遇到第 1 集的日子是 posBposB,这里有三种情况:

  1. posAposBposA \neq posB,当产生分歧的时候,这个区间就用不了了,设 posA<posBposA < posB,这个起点 LL 最远就能走到 min(posA,posB)1\min(posA, posB) - 1
  2. posA=posB=idxposA = posB = idx,在 idxidx 这一天,两人同时看到了第 1 集。看完后,两人的目标同时变成了第 2 集。 那么从 idx+1idx + 1 天开始,他们寻找第 2 集的过程就变成了下一个阶段的问题。
  3. posA=posB=posA = posB = \infty,如果在剩下的日子里,第 1 集再也没出现过,那两个人就什么也不看,此时动作也是永远一致的,一直到第 nn 天结束都是合法的。

观察第二种情况,很明显满足无后效性。当我们同时在第 idxidx 天看完第 vv 集后,我们接下来的命运,只取决于从 idx+1idx + 1 天开始寻找第 v+1v+1 集的结果

定义 DP[i]DP[i]为:假设两人在第 ii 天成功同步看完了第 vv 集(前提 ai=bi=va_i = b_i = v),那么在未来,他们最早在哪一天会产生分歧(即动作不一致)

状态计算:在第 ii 天看完后,目标变成了 ai+1a_i + 1。我们去找 ai+1a_i + 1ii 之后的首次出现位置 posAposAposBposB

  • 如果 posAposBposA \neq posB,意味着两人寻找下一集遇到了时间差,那分歧必然在较早的那一天发生。所以 DP[i]=min(posA,posB)DP[i] = \min(posA, posB)
  • 如果 posA=posB=next_idxposA = posB = next\_idx,说明又同步了,分歧点推迟到了下一次,所以 DP[i]=DP[next_idx]DP[i] = DP[next\_idx]
  • 如果都没出现,说明永远没有分歧,合法的尽头是最后一天之后,DP[i]=n+1DP[i] = n + 1

Code#

NOTE

代码中,p1p2 是二维数组(类似于邻接表)。p1[v] 里面按升序存储了第 vv 集在 Alice 城市播放的所有天数。

find 函数的作用是,在时刻表 s 里,找到大于等于第 xx的第一个播放日。 如果没有找到,说明以后这部剧断更了,返回 inf

使用倒序的 DP 进行预处理,寻找首次分歧日,并枚举每一个可能的起点 L=iL = i,计算答案。

// Problem: CF 2230 D
// Contest: Codeforces - Educational Codeforces Round 190 (Rated for Div. 2)
// URL: https://codeforces.com/contest/2230/problem/D
// Time: 2026-05-20 23:19:09
#include <bits/stdc++.h>
using namespace std;
// clang-format off
#define endl '\n'
#define int long long
#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>;
using i128 = __int128;
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), b(n + 1);
vector<vector<int>> p1(n + 2), p2(n + 2);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
p1[a[i]].push_back(i);
}
for (int i = 1; i <= n; i++)
{
cin >> b[i];
p2[b[i]].push_back(i);
}
auto find = [&](const vector<int> &s, int x)
{
auto p = lower_bound(s.begin(), s.end(), x);
if (p == s.end())
return inf;
return *p;
};
vector<int> dp(n + 1);
for (int i = n; i > 0; i--)
{
if (a[i] != b[i])
continue;
int na = find(p1[a[i] + 1], i + 1);
int nb = find(p2[a[i] + 1], i + 1);
if (na == nb)
{
if (na == inf)
dp[i] = n + 1;
else
dp[i] = dp[na];
}
else
dp[i] = min(na, nb);
}
int res = 0;
for (int i = 1; i <= n; i++)
{
int na = find(p1[1], i);
int nb = find(p2[1], i);
if (na == nb)
{
if (na == inf)
res += n - i + 1;
else
res += dp[na] - i;
}
else
res += min(na, nb) - i;
}
cout << res << endl;
}
signed main()
{
fastio();
int T = 1;
cin >> T;
while (T--)
solve();
return 0;
}