A. Set

题意 给一个包含区间 [l, r][l, \ r] 中所有整数组成一个集合 SS 和一个整数 kk。一次操作是在集合 SS 中选择一个数字 xx,满足 SS 中至少有 kkxx 的倍数,从 SS 中删除 xx。求最大操作次数。

思路 一个数 xx 的最小的 kk 个倍数分别是 x,2x,..,kxx,2x,..,kx,如果这些数都存在于集合中,也就是说 kxrkx \leqslant r,就可以删去 xx。基于贪心,从小到大依次删去 xx

最终能删去的 xx 满足 lxrkl \leqslant x \leqslant \lfloor \dfrac{r}{k} \rfloor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <bits/stdc++.h>
using namespace std;

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

int t;
cin >> t;

while (t--) {
int l, r, k;
cin >> l >> r >> k;

cout << max(0, r / k - l + 1) << endl;
}

return 0;
}

B. Replacement

题意 给一个长度为 nn0101 字符串 ss 和一个长度为 n1n - 10101 字符串 rr。可以对 ss 执行 n1n-1 次操作, 在第 ii 次操作中, 选择一个 kk ,满足 sksk+1s_{k} \neq s_{k + 1},并将 sksk+1s_{k}s_{k + 1} 替换为 rir_{i}。问能否执行完 n1n-1 次操作。

思路 对于每次操作,首先 ss 必须包含相邻的 01011010(若不然,ss 为全 00 串或全 11 串),如果 ri=0r_{i}=0,相当于从 ss 中删去一个 11,如果 ri=1r_{i}=1,相当于从 ss 中删去一个 00。每次看能否操作即可。

时间复杂度 Θ(n)\Theta(n)

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
#include <bits/stdc++.h>
using namespace std;

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

int t;
cin >> t;

while (t--) {
int n;
cin >> n;

string s;
cin >> s;

int cnt[2]{};
for (int i = 0; i < n; i++) {
cnt[s[i] - '0']++;
}

cin >> s;

bool ok = 1;
for (int i = 0; i < n - 1; i++) {
if (cnt[s[i] - '0'] > 0 && cnt['1' - s[i]] > 0) {
cnt['1' - s[i]]--;
} else {
ok = 0;
}
}

if (ok) {
cout << "YES\n";
} else {
cout << "NO\n";
}
}

return 0;
}

C. New Rating

题意 你的 rating 是 xx,初始为 00 ,给定一个数组 aa,遍历数组 aa

  1. ai>xa_{i} > x , x=x+1x= x+1
  2. ai=xa_{i}=x , xx 不变
  3. ai<xa_{i} < x , x=x1x = x-1

可以选择跳过 aa 中的一个 Hack 区间(至少跳过一个数),最大化最终的 rating。

思路一 问题转化为是否存在某个前缀和某个后缀满足题意。

记录前缀 rating 的最大值 uu 和后缀 rating 的最小值 vv,如果 uvu \geqslant v,就可以选择某个 x[v,u]x\in[v,u],当前缀 rating 为 xx 后进入 Hack 区间,在后缀 rating 为 xx 后退出 Hack 区间。

由于初始 rating 为 0,前缀 rating 可以直接求得。后缀不好求,但发现后缀最小值关于最终 rating 具有单调性,因此可以二分最终 rating,再直接求得后缀最小值。

时间复杂度 Θ(nlogn)\Theta(n\log n)

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
#include <bits/stdc++.h>
using namespace std;

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

int t;
cin >> t;

while (t--) {
int n;
cin >> n;

vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}

auto check = [&](int x) {
vector<int> pre(n), suf(n, x);
int now = 0;
for (int i = 0; i < n - 1; i++) {
if (now > a[i]) now--;
else if (now < a[i]) now++;
pre[i + 1] = max(pre[i], now);
}
now = x;
for (int i = n - 1; i; i--) {
if (now > a[i]) now++;
else if (now <= a[i]) now--;
suf[i - 1] = min(suf[i], now);
}

for (int i = 0; i < n; i++) {
if (pre[i] >= suf[i]) return true;
}
return false;
};

int l = 0, r = n;
while (l <= r) {
int mid = l + r >> 1;
if (!check(mid)) r = mid - 1;
else l = mid + 1;
}
cout << r << endl;
}

return 0;
}

思路二 dpi,jdp_{i,j} 表示选完前 ii 个成绩,状态为 jj,其中

  • j=0j=0,还未进入 Hack 区间

  • j=1j = 1,正在 Hack 区间

  • j=2j=2,已经退出 Hack 区间

存的值为最大 rating。第一维可省略。

时间复杂度 Θ(n)\Theta(n)

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
#include <bits/stdc++.h>
using namespace std;

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

int t;
cin >> t;

while (t--) {
int n;
cin >> n;

int dp0 = 0, dp1 = -n, dp2 = -n;
for (int i = 0; i < n; i++) {
int a;
cin >> a;

dp2 = max(dp2, dp1);
dp1 = max(dp1, dp0);

if (dp0 > a) dp0--;
else if (dp0 < a) dp0++;

if (dp2 > a) dp2--;
else if (dp2 < a) dp2++;
}

cout << max(dp1, dp2) << endl;
}

return 0;
}

D. Cool Graph

考虑边多了(也就是有环)怎么办,边少了(有边但不联通)怎么办。

如果某个点 xx 的度超过了 22,就删去其中两条边 xy, xzx-y,\ x-z,即操作 (x,y,z)(x,y,z),直到度小于 22。这样每次操作至少会减少一条边,操作次数不会超过 mm

如果没有边,已经满足题意。

现在这张图是一些孤立的边。对于某两个连通块,设其中一个连通块有边 xyx-y,选择另一个连通块中任意一个点 zz,操作 (x,y,z)(x,y,z),就可以把这两个联通块连通,而且连通后仍然是一棵树。初始至多 nn 个连通块,每次操作至少会减少一个连通块,操作次数不会超过 nn

总的操作次数不会超过 m+nm+n

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
61
62
63
64
65
66
67
68
69
#include <bits/stdc++.h>
using namespace std;

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

int t;
cin >> t;

while (t--) {
int n, m;
cin >> n >> m;

vector<map<int, bool>> E(n);
auto add = [&](int u, int v) {
if (E[u].count(v)) E[u].erase(v), E[v].erase(u);
else E[u][v] = E[v][u] = 1;
};
auto get = [&](int u) {
return (*E[u].begin()).first;
};

while (m--) {
int u, v;
cin >> u >> v;
u--, v--;
add(u, v);
}

vector<array<int, 3>> res;
for (int i = 0; i < n; i++) {
while (E[i].size() > 1) {
int u = get(i); add(u, i);
int v = get(i); add(v, i);
add(u, v);
res.push_back({ i, u, v });
}
}

vector<int> vis(n);
int u = -1, v = -1;
for (int i = 0; i < n; i++) {
if (E[i].size() == 1) {
u = i, v = get(i);
vis[u] = 1;
vis[v] = 1;
break;
}
}

if (u != -1) {
for (int i = 0; i < n; i++) {
if (vis[i]) continue;
vis[i] = 1;
if (!E[i].empty()) vis[get(i)] = 1;
res.push_back({ i, u, v });
u = i;
}
}

cout << res.size() << endl;
for (auto [x, y, z] : res) {
cout << ++x << " " << ++y << " " << ++z << endl;
}
}

return 0;
}

UPD:可以直接枚举给的每一条边,只要 u,vu, v 都不是 1,就用 1 把 u,vu, v 消掉,最后只剩下若干个孤立点和一个星状图,把孤立点再逐个连接到 1 上。操作次数也是不超过 m+nm+n

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
#include <bits/stdc++.h>
using namespace std;

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

int t;
cin >> t;

while (t--) {
int n, m;
cin >> n >> m;

map<int, int> E;
auto add = [&](int u) {
if (E.count(u)) E.erase(u);
else E[u] = 1;
};

vector<array<int, 3>> res;
while (m--) {
int u, v;
cin >> u >> v;
u--, v--;
if (u == 0) {
add(v);
} else if (v == 0) {
add(u);
} else {
res.push_back({ 0, u, v });
add(u), add(v);
}
}

if (!E.empty()) {
int u = (*E.begin()).first;
for (int v = 1; v < n; v++) {
if (E.count(v)) continue;
res.push_back({ 0, v, u });
u = v;
}
}

cout << res.size() << endl;
for (auto [x, y, z] : res) {
cout << ++x << " " << ++y << " " << ++z << endl;
}
}

return 0;
}

E. Common Generator

题意 对于 xx,定义一次操作为选择 xx 的一个非 11 因子 dd,置 xx+dx\leftarrow x+d,如果若干次操作后 x=yx=y,就称初始 xx 能变成 yy。现在给定一个集合 a (ai2)a\ (a_{i} \geqslant 2),问是否存在一个数 x (x2)x\ (x\geqslant2) 使得 xx 能变成所有 aia_{i}

思路 没有任何数可以变成素数,因此集合中只能出现一个素数,且这个素数就是答案。22 可以变成任何合数(1×2s0×2s0×s11\times 2\to s_{0}\times 2\to s_{0}\times s_{1}),因此如果集合中没有素数,就让答案为 22

现在检验所选素数 pp 能否变成所有 aia_{i}

如果 aia_{i}pp 的倍数,是可行的。否则,设 ai=s0s1a_{i}=s_{0}\cdot s_{1}。希望 1×p2×ps0×s11\times p\to 2\times p\to\dots\to s_{0}\times s_{1},发现如果 s02,s1ps_{0} \geqslant 2,s_{1} \geqslant p 就可行,因此基于贪心,s0s_{0} 应当取最小素因子。这并不是全部可行情形,可以先 2×p2×s0d2\times p\to 2\times s_{0}d,再 s0×2ds0×s1s_{0}\times 2d\to s_{0}\times s_{1},所以说,如果存在某个 dd 满足 ps0d2ds1p \leqslant s_{0}d \land 2d \leqslant s_{1} 就可行。这个 dd 可以在 Θ(1)\Theta(1) 时间内找出并判断。

时间复杂度 Θ(n)\Theta(n)

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
61
#include <bits/stdc++.h>
using namespace std;

constexpr int N = 4e5 + 8;
vector<int> prime;
int mint[N];

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

for (int i = 2; i < N; ++i) {
if (!mint[i]) mint[i] = i, prime.push_back(i);
for (auto& p : prime) {
if (1ll * i * p >= N) break;
mint[i * p] = p;
if (p == mint[i]) break;
}
}
mint[1] = 1;

int t;
cin >> t;

while (t--) {
int n;
cin >> n;

vector<int> a(n);
int p = -1, ok = true;
for (int i = 0; i < n; i++) {
cin >> a[i];
if (mint[a[i]] == a[i]) {
if (p == -1) p = a[i];
else ok = false;
}
}
if (p == -1) p = 2;

for (int i = 0; i < n; i++) {
if (a[i] == p) continue;
int d;
if (a[i] & 1) {
d = 2 * (p + mint[a[i]] - 1) / mint[a[i]];
} else {
d = p;
}
if (d > a[i] / mint[a[i]]) {
ok = false;
}
}

if (!ok) {
cout << -1 << endl;
} else {
cout << p << endl;
}
}

return 0;
}

F. Palindrome Everywhere

又是 01 串,又是奇偶。。

如果同时出现 RR 和 BB 就无解。

否则,如果有 RR,就把 R 变成 1,B 变成 0,如果有 BB,就把 B 变成 1,R 变成 0。

这个 01 串总是一个 1 串接上一个 0,再一个 1 串接上一个 0 这样循环的,统计每个 1 串的长度。而且不关心具体长度,只关心奇偶性(因为可以在某个 1 上面绕圈,而绕圈不改变奇偶)。

结论是:

  • 长度为偶数的 1 串超过一个,无解(以其中一个偶串的端点为起点,以另一个偶串的第一个位置为终点,这样,出发时必然经过一个偶串,到达时必然经过一个奇串,不合法)

  • 长度为偶数的 1 串恰好一个,有解(具体构造方法可参考样例三的解释,构造出的回文串仅包含两个长度为偶数的 1 串,其中一个是原串里的,另一个是在长度为奇数的 1 串上「掉头」产生的)

  • 长度为偶数的 1 串不存在,这时当且仅当长度为奇数的 1 串恰好有一个时有解(有解是显然的;如果有两个长度为奇数的 1 串,分别以这两个串的左端点为起点和终点就无解)

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
61
#include <bits/stdc++.h>
using namespace std;

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

int t;
cin >> t;

while (t--) {
int n;
string s;
cin >> n >> s;

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

if (p == -1) {
cout << "YES\n";
continue;
}

s = s.substr(p) + s.substr(0, p);

int R = s.find("RR");
int B = s.find("BB");

if (R != -1 && B != -1 || R == -1 && B == -1) {
cout << "NO\n";
continue;
}

char base = (R != -1) ? 'R' : 'B';

int cnt[2]{};
int k = 0;
for (auto& c : s) {
if (c == base) {
k++;
} else {
if (k) cnt[k & 1]++;
k = 0;
}
}
if (k) cnt[k & 1]++;

if (cnt[0] == 1 || cnt[1] <= 1 && cnt[0] == 0) {
cout << "YES\n";
} else {
cout << "NO\n";
}
}

return 0;
}