Codeforces Round 972 (Div. 2)

写在前面

比赛地址:https://codeforces.com/contest/2005

唉唉赛时 C 烂掉了要不然要上大分现在只能上小分了——如果 C 最后没 fst 的话呃呃现在只能掉小分了。

妈的所以我赛时 C 吃了 6 发最后还是没过啊,真是坏透了。

A 签到

发现应当尽量不然两个相同的字符之间有任何字符,比如 a....a,否则一定会构成超级多回文子序列。

然后发现每种字符应当尽可能少,于是考虑将数量平均分到所有字符上,然后按顺序排列即可。

复制复制
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
int n; std::cin >> n;
char s[10] = "aeiou";
int len = n / 5, r = n % 5;
for (int i = 0; i < 5; ++ i) {
for (int j = 1; j <= len; ++ j) {
std::cout << s[i];
}
if (r) std::cout << s[i], -- r;
}
std::cout << "\n";
}
return 0;
}

B1/B2 模拟,结论,二分

发现仅需考虑与逃脱者相邻的两个老师即可:

  • 若逃脱者左边没有老师,则最优的方案是逃到最左边然后呆着;右侧没有老师同理;
  • 否则逃脱者应当逃到两个老师位置的中点。

于是考虑用 set 维护所有老师的位置,每次查询前驱后继,O(1) 计算答案即可。

B2 代码:

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, m, q;
int b[kN];
std::set<int> s;
//=============================================================
int query(int x_) {
if (x_ < b[1]) return b[1] - 1;
if (x_ > b[m]) return n - b[m];
auto p = s.lower_bound(x_);
auto q = p; -- q;
return (*p - *q) / 2;
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> m >> q;
s.clear();
for (int i = 1; i <= m; ++ i) std::cin >> b[i], s.insert(b[i]);
std::sort(b + 1, b + m + 1);
while (q --) {
int x; std::cin >> x;
std::cout << query(x) << "\n";
}
}
return 0;
}

C DP

呃呃裸 DP,题面写的一坨理解错了叫我嗯吃六发,然后还 fst 了真是被这题鲨完了。

发现实际有贡献的字符集大小仅有 5,考虑记 narek 分别为 04。考虑预处理 alli 表示字符串 inarek 的数量。

发现可以分别考虑每个字符串怎么取,仅需知道在这一字符串第一个取哪一个字符,即可唯一确定取完这一段可以拿到几个字符,以及最后取到的是哪个字符。

于是考虑先预处理一下 cnti,j,endi,j,分别表示当前取的是第 i 个字符串,第一个必须取字符 j,可以取到的字符的最多数量,以及最后取到的是哪个字符。字符集大小只有 5 直接大力 O(nm) 枚举预处理即可。

又限定不能交换选择的字符串的顺序,于是考虑 DP,记 fi,k 表示当前最后选的不大于第 i 个字符串,对这一字符串按照给定规则选完字符之后,选的最后一个字符为 k,可以获得的最大价值。转移时考虑上一个字符串最后的字符 k,则有显然的转移:

{fi,kfi1,kfi,endi,kfi1,k+cnti,(k+1)mod5(allicnti,(k+1)mod5)

统计答案时,考虑枚举最后一段选的是哪个字符串 i,以及最后一段的可能不完整的 narek 的长度 k 即可,若最后一段不完整则需要减掉他们的贡献,则有:

ans{fi,k(j=4)fi,k2×(k+1)(j<4)

复杂度 O(nm) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
const char ch[10] = "narek";
const int kInf = 1e6;
//=============================================================
int n, m;
std::string s[kN];
int all[kN];
int f[kN][6], cnt[kN][6], end[kN][6];
int yes[500], rk[500];
//=============================================================
//=============================================================
signed main() {
// freopen("1.txt", "r", stdin);
for (int i = 0; i < 5; ++ i) yes[(int) ch[i]] = 1, rk[(int) ch[i]] = i;
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i) {
std::cin >> s[i];
all[i] = 0;
for (auto c: s[i]) if (yes[(int) c]) ++ all[i];
s[i] = "$" + s[i];
}
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j < 5; ++ j) {
cnt[i][j] = 0, end[i][j] = -1;
for (int k = 1, l = s[i].length(), now = j; k < l; ++ k) {
if (!yes[(int) s[i][k]]) continue;
if (rk[(int) s[i][k]] != now) continue;
++ cnt[i][j], end[i][j] = now;
now = (now + 1) % 5;
}
}
}
int ans = 0;
for (int i = 0; i <= n; ++ i) {
for (int j = 0; j < 5; ++ j) {
f[i][j] = -kInf;
}
}
f[0][4] = 0;
for (int i = 1; i <= n; ++ i) {
for (int k = 0; k < 5; ++ k) f[i][k] = f[i - 1][k];
for (int k = 0; k < 5; ++ k) {
int p = (k + 1) % 5;
if (cnt[i][p] == 0) continue;
f[i][end[i][p]] = std::max(f[i][end[i][p]],
f[i - 1][k] + cnt[i][p] - (all[i] - cnt[i][p]));
if (end[i][p] == 4) ans = std::max(ans, f[i][end[i][p]]);
else ans = std::max(ans, f[i][end[i][p]] - 2 * (end[i][p] + 1));
}
}
std::cout << ans << "\n";
}
return 0;
}
/*
1
1 6
narekn
1
1 11
nareknareek
1
2 5
zzrrn
areka
*/

D 数学,结论

周所周知区间 gcd 可以 ST 表 O(nlognlogv) 预处理 O(logv) 查询,并且常数很小。

众所周知序列 gcd 是有单调性的,序列越长 gcd 单调不降,且每次下降至少令 gcd 除 2。

考虑枚举被操作区间的左端点 l,然后考虑右端点 r 的取值对答案的影响。发现此时可以将两个数列分成六段:

  • gcd(a1al1),gcd(b1bl1):因为枚举左端点已确定。
  • gcd(alar),gcd(blbr)
  • gcd(ar+1an),gcd(br+1bn)

由上述结论可知,后四段至多仅会随着 r 的移动变化 O(logn) 次,则有贡献的 r 仅有至多 4logn 段。于是考虑枚举左端点 l,然后大力二分求得上述至多 4logn 段的断点并查询。

总时间复杂度 O(nlog2nlogv) 级别,赛时可过但是赛后被卡了呃呃。

每次二分求得端点这个 logn 要不得,考虑能否直接动态维护这 4logn 段,并每次仅加入新增贡献的段。发现仅需考虑倒序枚举 l,然后考虑将所有段与新加入贡献的 al+1 与它们取 log,即可 O(lognlogv) 维护所有段。之后再枚举所有段的断点统计贡献即可。

总时间复杂度 O(nlognlogv) 级别。

E1 DP

发现每个人选的数是唯一确定的,一个显然的想法是考虑当前选到哪个数,且当前这个人要选什么位置,则下一个人仅需考虑该位置右下的矩形即可。

于是想到一个显然的 DP,设 fk,0/1,i,j 表示当前要选第 k 个数了,第 0/1 个玩家操作,这个玩家这一步选择 (i,j),是否必胜,则转移时仅需考虑 fk+1,1,i+1n,j+1m 这些状态里有没有 1 即可。发现是个矩形求和的形式,仅需额外对 f 维护一个二维前缀和即可。

因为每个人选的数是唯一确定的,发现 f 的第一维是无必要的,滚一下就行了。

总时间复杂度 O(nml) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 300 + 10;
//=============================================================
int l, n, m, a[kN], b[kN][kN];
int f[2][kN][kN], sum[2][kN][kN];
//=============================================================
void init() {
for (int p = 0; p <= 1; ++ p) {
for (int i = 1; i <= n + 1; ++ i) {
for (int j = 1; j <= m + 1; ++ j) {
f[p][i][j] = sum[p][i][j] = 0;
}
}
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> l >> n >> m;
for (int i = 1; i <= l; ++ i) std::cin >> a[i];
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
std::cin >> b[i][j];
}
}
init();
for (int k = l; k; -- k) {
int p = (k & 1);
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
f[p][i][j] = 0;
if (b[i][j] != a[k]) continue;
if (sum[p ^ 1][i + 1][j + 1] == 0) f[p][i][j] = 1;
}
}
for (int i = n; i; -- i) {
for (int j = m; j; -- j) {
sum[p][i][j] = f[p][i][j];
sum[p][i][j] += sum[p][i + 1][j] + sum[p][i][j + 1] - sum[p][i + 1][j + 1];
}
}
}
int flag = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
if (f[1][i][j] == 1) {
flag = 1;
break;
}
}
if (flag) break;
}
std::cout << (flag ? "T" : "N") << "\n";
}
return 0;
}

E2 贪心,DP

感觉 E1 的做法已经很优美了,但是不可避免地需要枚举 l,但 E2 仅保证了 nm 的上界,于是需要把 l 从复杂度里删掉。

写在最后

学到了什么:

  • C:注意数据范围中保证的是什么东西的上界!!!

妈的什么时候才能上黄、、、

然后夹带一下私货吧呜呜,关注 RESCAT 喵关注 RESCAT 谢谢喵:

posted @   Luckyblock  阅读(588)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示