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 \cfrac{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;
}