Codeforces Global Round 28

比赛链接

Problems AC Note
A. Kevin and Combination Lock ○
B. Kevin and Permutation ○
C. Kevin and Binary Strings ○
D. Kevin and Competition Memories ○
E. Kevin and Bipartite Graph ⊕ 二分图、构造
F. Kevin and Math Class ⊕ 笛卡尔树、树形 DP
G. Kevin and Matrices
H. Kevin and Strange Operation
I1. Kevin and Puzzle (Easy Version)
I2. Kevin and Puzzle (Hard Version)

A

直接蜘蛛感应出做法了,懒得证了。

Code >folded
1
2
3
4
5
6
7
8
void solve() {
int n = read();
if (n % 33 == 0) {
puts("YES");
} else {
puts("NO");
}
}

B

最优一定是间隔放置,很典

Code >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void solve() {
int n = read();
int k = read();

vector<int> a(n + 1);
int cnt = 1;
for (int i = k; i <= n; i += k) {
a[i] = cnt++;
}
for (int i = 1; i <= n; i++) {
if (!a[i]) a[i] = cnt++;
}

for (int i = 1; i <= n; i++) {
cout << a[i] << " \n"[i == n];
}
}

C

赛时做法是把所有字符串扔到 Trie 里面,然后在 Trie 上面 dfs,似乎是写复杂了

Code >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void solve() {
string s;
cin >> s;
int n = s.size();

int p = -1;
for (int i = 1; i < n; i++) {
if (s[i] == '0') {
p = i;
break;
}
}

if (p == -1) {
cout << "1 1 1 " << n << '\n';
return;
}

int len = n - 1 - p + 1;
int nod = 0;
std::unordered_map<int, int> ch[2], info;

pii ans;
for (int i = 0; i + len - 1 < n; i++) {
int cur = 0;
for (int j = i; j <= i + len - 1; j++) {
int x = s[j] - '0';
if (!ch[x][cur]) {
ch[x][cur] = ++nod;
}
// assert(cur < ch.size());
cur = ch[x][cur];
}
info[cur] = i + 1;
}

int cur = 0;
for (int i = p; i < n; i++) {
int x = s[i] - '0';
if (ch[x ^ 1][cur]) {
cur = ch[x ^ 1][cur];
} else {
cur = ch[x][cur];
}
}

cout << "1 " << n << ' ';
cout << info[cur] << ' ' << (info[cur] + len - 1) << '\n';
}

D

随便写写就过了。

Code >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void solve() {
int n = read();
int m = read();
vector<int> a(n + 1);
vector<int> b(m + 1);
for (int i = 1; i <= n; i++) a[i] = read();
std::sort(a.begin() + 2, a.end());
for (int i = 1; i <= m; i++) b[i] = read();
std::sort(b.begin() + 1, b.end());

int cnt = 0;
int le = 0, ri = -1;
for (int i = 1; i <= m; i++) {
if (b[i] <= a[1] or b[i] > a[n]) {
cnt++;
} else {
if (le == 0) {
le = i;
}
ri = i;
}
}

vector<int> c(m + 1);
for (int i = le; i <= ri; i++) {
for (int L = 2, R = n; L <= R; ) {
int mid = L + R >> 1;
if (a[mid] >= b[i]) {
c[i] = n - mid + 1;
R = mid - 1;
} else {
L = mid + 1;
}
}
}

for (int k = 1; k <= m; k++) {
int tot = m / k;
int tcnt = cnt;

int ans = 0;
int l = le, r = ri;

for (int i = 1; i <= tot; i++) {
int need = k;
int rk = 1;

int tmp = min(tcnt, need);
tcnt -= tmp, need -= tmp;

tmp = min(need, r - l + 1);
r -= tmp, need -= tmp;
if (r + 1 <= m) rk = 1 + c[r + 1];
ans += rk;
}

cout << ans << " \n"[k == m];
}
}

E

一个完全二分图,左部有 $2n$ 个点,右部有 $m$ 个点,给每个边染上 $[1,n]$ 的颜色,使得没有同色简单环。

唉又被构造单杀了。前几天刚 VP 完 2022 ICPC 澳门,这个题关键点和那个差不多,都是算边数限制的。

由 $2nm \le n(2n + m - 1)$ 得 $m \le 2n - 1$。

$m$ 越大时限制越紧,尝试构造 $m = 2n - 1$ 的解。

此时每个右部点度数是 $2n$,一共有 $n$ 中颜色,令每种颜色的边恰好 $2$ 条。那么对于每个颜色,就相当于通过这个右部点,使两个左部点连通。最后经过 $m = 2n - 1$ 次连接,左部点需要形成树。可以让左边直接形成一条链,每次 shift 一下颜色即可。

在这种情况下,边 $(i, j)$ 的颜色为 $\lfloor \frac{(i + j) \bmod 2n}{2} \rfloor + 1$

Code >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void solve() {
int n = read();
int m = read();

if (m >= n * 2) {
puts("NO");
return;
}

puts("YES");
for (int i = 1; i <= 2 * n; i++) {
for (int j = 1; j <= m; j++) {
int t = (i + j) % (2 * n) / 2 + 1;
cout << t << " \n"[j == m];
}
}
}

F

Kevin 是 Eversleeping Town 的一名学生,目前正在上数学课,老师正在给他布置除法练习。

黑板上写着两行正整数,每行包含 $ n $ 个数字。第一行是 $ a _ 1, a _ 2, \ldots, a _ n $ ,第二行是 $ b _ 1, b _ 2, \ldots, b _ n $ 。

对于每个除法练习,Kevin 可以选择任意段 $ [l, r] $ ,并在 $ b _ l, b _ {l+1}, \ldots, b _ r $ 中找到最小值 $ x $ 。然后,他将 $ l \leq i \leq r $ 中的每个 $ a _ i $ 修改为 $ a _ i $ 除以 $ x $ 的上限。

正式来说,他选择了两个整数 $ 1 \leq l \leq r \leq n $ ,设置了 $ x = \min _ {l \leq i \leq r} b _ i $ ,并将所有 $ a _ i $ 中的 $ l \leq i \leq r $ 更改为 $ \lceil \frac{a _ i}{x} \rceil $ 。

当所有 $ a _ i $ 都变为 $ 1 $ 时,Kevin 就可以下课回家了。他很想离开,想知道实现这一目标所需的最少除法练习次数。

前两天 VP icpc 杭州刚做过类似的。首先显然你要搞个笛卡尔树,考虑在笛卡尔树上面 DP。

令 $f(u, i)$ 代表 $u$ 这个点操作 $i$ 次后,区间内的 $a$ 的最大值最小是多少。

因为 $b \ge 2$,所以最多 $\log$ 次操作。那么应该可以类似树形背包,枚举左右子树选了多少个,然后做合并。

然而这道题卡 2log。观察发现 $f(u, i)$ 是单调不增的。因此做 $\min - \max$ 卷积等价于对两个序列做归并排序。那么就是一只 log 了。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const int K = 61;
int ceil_div(int x, int y) { return x / y + (x % y != 0); }
void solve() {
int n = read();

vector<int> a(n), b(n);
for (auto &x : a) x = read();
for (auto &x : b) x = read();

vector<int> lc(n, n), rc(n, n);
vector<int> stk;
for (int i = 0; i < n; i++) {
while (!stk.empty() and b[i] < b[stk.back()]) {
rc[stk.back()] = lc[i];
lc[i] = stk.back();
stk.pop_back();
}
stk.eb(i);
}
while (stk.size() > 1) {
int x = stk.back();
stk.pop_back();
rc[stk.back()] = x;
}


vector<std::array<int, K>> dp(n + 1);
auto dfs = [&](this auto &&self, int x, int l, int r) {
if (x == n) return;
self(lc[x], l, x);
self(rc[x], x + 1, r);
auto &L = dp[lc[x]];
auto &R = dp[rc[x]];
auto &f = dp[x];
int i = 0, j = 0;
f[0] = std::max({L[0], R[0], a[x]});
while (i + j + 1 < K) {
if (L[i] > R[j]) {
i++;
} else {
j++;
}
f[i + j] = max({L[i], R[j], a[x]});
}
for (int i = 1; i < K; i++) {
f[i] = min(f[i], ceil_div(f[i - 1], b[x]));
}
};
dfs(stk[0], 0, n);

auto &f = dp[stk[0]];
int ans = std::find(f.begin(), f.end(), 1) - f.begin();
cout << ans << "\n";
}
Author

TosakaUCW

Posted on

2024-12-21

Updated on

2025-01-03

Licensed under

Comments