NOTE
A - Optimal Purchase
关键词:模拟
思路
考虑三种情况:
- 为所有的学生买个人秘钥
- 为
n / 3组学生买团体秘钥,并为n % 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
关键词:枚举,前缀和
思路
注意到所有的 都不能选,因为总可以选择 本身作为构成 的倍数的元素。
考虑哪些组合可以是 的倍数,只有如下几个:,而含有 的又不可以被选,因此只需要考虑 。
不能出现 的子序列,意味着所有的 和 都必须排在 的后面。
因此最终保留的合法字符一定是 的形式。
换句话说,整个合法的字符串一定可以被一条“分割线”切成两半:左半边只能包含 ,右半边:只能包含 和 。
使用前缀和维护 的出现次数,枚举分割点,并计算最多能保留下来的字符数。
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 种基础牌 (假设该数字有 张),每隔绝一个孤儿就需要左右两侧各有一个 ,所以 个孤儿需要 个 。 容纳上限为:。
- 有 种基础牌 (比如 ),为了保证基础牌之间不发生违规,我们让每种基础牌各自抱团,形如 。 在这个大的圆环里,基础牌 除了要保护内部的孤儿,它的左边界和右边界还要去和别的字母交接。 为了防止边界出现违规,基础牌 必须留出“一头一尾”各 1 张牌去当屏障。所以,只有剩下的 张牌可以起到庇护作用。 此时,基础牌 能容纳的孤儿数量上限变成了:。
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
思路
两个人追剧必须是按顺序来的,也就是开始的目标集都是第一集,如果电视放的是目标集就看,看完后目标集变成下一集,否则他们会无视。我们需要找一个区间 ,在这个区间里,要么一起看同一集,要么一起啥也不看。这就意味着,他们在整个区间里,目标集永远是同步的。
假设我们固定了区间的起点 ,初始时两人都需要看第 1 集。
接下来会发生什么?我们寻找第 1 集在 和 中首次出现的日子:设 Alice 首次遇到第 1 集的日子是 ,设 Bob 首次遇到第 1 集的日子是 ,这里有三种情况:
- ,当产生分歧的时候,这个区间就用不了了,设 ,这个起点 最远就能走到 。
- ,在 这一天,两人同时看到了第 1 集。看完后,两人的目标同时变成了第 2 集。 那么从 天开始,他们寻找第 2 集的过程就变成了下一个阶段的问题。
- ,如果在剩下的日子里,第 1 集再也没出现过,那两个人就什么也不看,此时动作也是永远一致的,一直到第 天结束都是合法的。
观察第二种情况,很明显满足无后效性。当我们同时在第 天看完第 集后,我们接下来的命运,只取决于从 天开始寻找第 集的结果。
定义 为:假设两人在第 天成功同步看完了第 集(前提 ),那么在未来,他们最早在哪一天会产生分歧(即动作不一致)
状态计算:在第 天看完后,目标变成了 。我们去找 在 之后的首次出现位置 和 。
- 如果 ,意味着两人寻找下一集遇到了时间差,那分歧必然在较早的那一天发生。所以 。
- 如果 ,说明又同步了,分歧点推迟到了下一次,所以 。
- 如果都没出现,说明永远没有分歧,合法的尽头是最后一天之后,。
Code
NOTE代码中,
p1和p2是二维数组(类似于邻接表)。p1[v]里面按升序存储了第 集在 Alice 城市播放的所有天数。
find函数的作用是,在时刻表s里,找到大于等于第 天的第一个播放日。 如果没有找到,说明以后这部剧断更了,返回inf。使用倒序的
DP进行预处理,寻找首次分歧日,并枚举每一个可能的起点 ,计算答案。
// 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;}