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
分别为 \(0\sim 4\)。考虑预处理 \(\operatorname{all}_i\) 表示字符串 \(i\) 中 narek
的数量。
发现可以分别考虑每个字符串怎么取,仅需知道在这一字符串第一个取哪一个字符,即可唯一确定取完这一段可以拿到几个字符,以及最后取到的是哪个字符。
于是考虑先预处理一下 \(\operatorname{cnt}_{i, j}, \operatorname{end}_{i, j}\),分别表示当前取的是第 \(i\) 个字符串,第一个必须取字符 \(j\),可以取到的字符的最多数量,以及最后取到的是哪个字符。字符集大小只有 5 直接大力 \(O(nm)\) 枚举预处理即可。
又限定不能交换选择的字符串的顺序,于是考虑 DP,记 \(f_{i, k}\) 表示当前最后选的不大于第 \(i\) 个字符串,对这一字符串按照给定规则选完字符之后,选的最后一个字符为 \(k\),可以获得的最大价值。转移时考虑上一个字符串最后的字符 \(k\),则有显然的转移:
统计答案时,考虑枚举最后一段选的是哪个字符串 \(i\),以及最后一段的可能不完整的 narek
的长度 \(k\) 即可,若最后一段不完整则需要减掉他们的贡献,则有:
复杂度 \(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(n\log n\log v)\) 预处理 \(O(\log v)\) 查询,并且常数很小。
众所周知序列 \(\gcd\) 是有单调性的,序列越长 \(\gcd\) 单调不降,且每次下降至少令 \(\gcd\) 除 2。
考虑枚举被操作区间的左端点 \(l\),然后考虑右端点 \(r\) 的取值对答案的影响。发现此时可以将两个数列分成六段:
- \(\gcd(a_1\sim a_{l-1}), \gcd(b_1\sim b_{l-1})\):因为枚举左端点已确定。
- \(\gcd(a_l\sim a_{r}), \gcd(b_{l}\sim b_{r})\);
- \(\gcd(a_{r+1}\sim a_{n}), \gcd(b_{r+1}\sim b_{n})\);
由上述结论可知,后四段至多仅会随着 \(r\) 的移动变化 \(O(\log n)\) 次,则有贡献的 \(r\) 仅有至多 \(4\log n\) 段。于是考虑枚举左端点 \(l\),然后大力二分求得上述至多 \(4\log n\) 段的断点并查询。
总时间复杂度 \(O(n\log^2 n\log v)\) 级别,赛时可过但是赛后被卡了呃呃。
每次二分求得端点这个 \(\log n\) 要不得,考虑能否直接动态维护这 \(4\log n\) 段,并每次仅加入新增贡献的段。发现仅需考虑倒序枚举 \(l\),然后考虑将所有段与新加入贡献的 \(a_{l+1}\) 与它们取 \(\log\),即可 \(O(\log n\log v)\) 维护所有段。之后再枚举所有段的断点统计贡献即可。
总时间复杂度 \(O(n\log n\log v)\) 级别。
E1 DP
发现每个人选的数是唯一确定的,一个显然的想法是考虑当前选到哪个数,且当前这个人要选什么位置,则下一个人仅需考虑该位置右下的矩形即可。
于是想到一个显然的 DP,设 \(f_{k, 0/1, i, j}\) 表示当前要选第 \(k\) 个数了,第 0/1 个玩家操作,这个玩家这一步选择 \((i, j)\),是否必胜,则转移时仅需考虑 \(f_{k+1, 1, i+1\sim n, j+1\sim m}\) 这些状态里有没有 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 仅保证了 \(n\cdot m\) 的上界,于是需要把 \(l\) 从复杂度里删掉。
写在最后
学到了什么:
- C:注意数据范围中保证的是什么东西的上界!!!
妈的什么时候才能上黄、、、
然后夹带一下私货吧呜呜,关注 RESCAT 喵关注 RESCAT 谢谢喵: