2061A - Kevin and Arithmetic

第一个偶数,后面都是奇数。

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
#include <bits/stdc++.h>
using ll = long long;
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 cnt = 0;
bool flag = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
cnt += x % 2;
flag |= x % 2 == 0;
}
if (flag) cnt++;
else cnt--;
cout << cnt << endl;
}

return 0;
}

2061B - Kevin and Geometry

腰尽可能长,上下底尽可能接近。

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

int main() {
int t;
cin >> t;

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

vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a.begin(), a.end(), greater<>());
int id = -1;
for (int i = 1; i < n; i++) {
if (a[i] == a[i - 1]) {
id = i;
break;
}
}

if (id == -1) {
cout << -1 << endl;
} else {
vector<int> res;
res.push_back(a[id]);
res.push_back(a[id]);

int lst = -1, now = -1;
int minn = 1e9, id1 = -1, id2 = -1;
for (int i = 0; i < n; i++) {
if (i == id || i == id - 1) {
continue;
}
lst = now;
now = i;
if (lst == -1) continue;
if (a[lst] - a[now] < minn) {
minn = a[lst] - a[now];
id1 = lst;
id2 = now;
}
}

res.push_back(a[id1]);
res.push_back(a[id2]);

sort(res.begin(), res.end(), greater<>());
if (res[0] < res[1] + res[2] + res[3]) {
cout << res[0] << " " << res[1] << " " << res[2] << " " << res[3] << endl;
} else {
cout << -1 << endl;
}
}
}

return 0;
}

2061C - Kevin and Puzzle

dpi,jdp_{i,j} 表示第ii 个人前面有jj 个人撒谎的方案数。

如果ii 为真,则dpi,ai=dpi2,ai1+dpi1,aidp_{i,a_{i}}=dp_{i-2,a_{i}-1}+dp_{i-1,a_{i}}。否则不转移。答案为dpn1,+dpn,dp_{n-1,*}+dp_{n,*}

虽然是二维 DP,但实际的键很少,只有2n2n,可以用 std::map 实现,复杂度O(nlogn)\operatorname{O}(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
52
53
54
55
56
#include <bits/stdc++.h>
using ll = long long;
using namespace std;

constexpr int mod = 998244353;

int main() {
int t;
cin >> t;

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

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

if (n == 1) {
cout << ((a[0] == 0) ? 2 : 1) << endl;
continue;
}

vector<map<int, int>> dp(n);
// 初始化
if (a[0] == 0) {
dp[0][0] = 1;
}
if (a[1] == 1) {
dp[1][1] = 1;
} else if (a[1] == 0 && a[0] == 0) {
dp[1][0] = 1;
}

// 转移
for (int i = 2; i < n; i++) {
dp[i][a[i]] = dp[i - 2][a[i] - 1] + dp[i - 1][a[i]];
dp[i][a[i]] %= mod;
}

// 答案
ll res = 0;
for (auto [_, v] : dp[n - 2]) {
res += v;
res %= mod;
}
for (auto [_, v] : dp[n - 1]) {
res += v;
res %= mod;
}
cout << res << endl;
}

return 0;
}

2061D - Kevin and Numbers

如果由x,yx,y 生成z=x+yz=x+y,那么x,yx,y 分别是z2\lfloor \cfrac{z}{2} \rfloorz2\lceil \cfrac{z}{2} \rceil,所以倒着看操作就是唯一的。

取出bb 中的最大值,如果在aa 中能找到,则同时删除,否则分成两半再塞进bb 里去,如此反复直至bb 空或bb 溢出。由于每次操作bb 数组数量会增加 1,所以至多操作nmn-m 次。

时间复杂度O(nlogn)\operatorname{O}(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
#include <bits/stdc++.h>
using ll = long long;
using namespace std;

int main() {
int t;
cin >> t;

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

multiset<int> a, b;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
a.insert(x);
}
for (int i = 0; i < m; i++) {
int x;
cin >> x;
b.insert(-x);
}

while (!b.empty() && b.size() <= a.size()) {
auto x = -(*b.begin());
b.erase(b.begin());
if (a.find(x) != a.end()) {
a.extract(x);
} else {
int y = x / 2, z = x - y;
b.insert(-y);
b.insert(-z);
}
}

cout << (a == b ? "Yes" : "No") << endl; // 其实是a,b皆空
}

return 0;
}

2061E - Kevin and And

首先想到的一种的贪心方法是,将aiai&bja_{i}\to a_{i}\&b_{j} 的所有差值塞进 priority_queue 里,贪心取比较大的,然后 WA2。这种贪心似乎有些问题,因为先 & 某个bib_{i} 或者先 & 另一个bib_{i} 的结果不一样,或者说每个数操作的「路径」不一样,贪心地选择某条路并不靠谱。

如果考虑 DP,可以预处理每个aia_{i} 升级jj 次的最优解,将每个aia_{i} 看作一组物品,做分组背包,但这样时空都是O(nk)\operatorname{O}(nk),无法通过。

同学提出「等级升级法」,称操作一次为「升一级」,提前预处理每个aia_{i} 升级jj 次的最优解,然后把每次升级的代价塞进 priority_queue 后贪心取比较大的。

这样就没有上面说的路径问题了,因为从jj 级提升到j+1j+1 级没有前后效性,与之前之后如何操作都没有关系,只与当前级别有关,就这一条路径。而且,每次升级的代价单调减,也就是 2 升 3 一定比 1 升 2 小,没有「能垒」。如果不是位与,是手动赋值等级一等于 1、等级二等于 2 等级三等于 9999999 那就完了,会被其他等级一等于 2 的竞争掉。现在所有东西都单调,且互不冲突,顺着贪就行。

时间复杂度O((n+m)2m+klogk)\operatorname{O}((n+m)\cdot 2^{m}+k\log k)10241051024\cdot 10^{5} 的复杂度是可以通过的。

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
#include <bits/stdc++.h>
using ll = long long;
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, k;
cin >> n >> m >> k;

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

// 预处理 b[j] 的 & 结果
vector<int> sum(1 << m, (1 << 30) - 1);
for (int bit = 0; bit < (1 << m); bit++) {
for (int j = 0; j < m; j++) {
if (bit >> j & 1) {
sum[bit] &= b[j];
}
}
}

priority_queue<int> Q;
for (int i = 0; i < n; i++) {
vector<int> dp(m + 1); // dp[j] : 升级 j 次的最大收益
for (int bit = 0; bit < (1 << m); bit++) {
// 这里不能再套一个 m 循环,会超时,改成上面提前预处理
// int sum = (1 << 30) - 1, cnt = 0;
// for (int j = 0; j < m; j++) {
// if (bit >> j & 1) {
// sum &= b[j];
// cnt++;
// }
// }
int cnt = __builtin_popcount(bit);
dp[cnt] = max(dp[cnt], a[i] ^ (a[i] & sum[bit]));
}
for (int j = 0; j < m; j++) {
Q.push(dp[j + 1] - dp[j]); // 等级提升一次的收益
}
}

ll ans = accumulate(a.begin(), a.end(), 0LL);
while (!Q.empty() && k--) {
ans -= Q.top();
Q.pop();
}
cout << ans << endl;
}

return 0;
}

2061F1 - Kevin and Binary String (Easy Version)

可以发现有解情况下,无论如何操作,操作次数都是一样的。

直接模拟交换,tt 的某块数不够就交换ss,如果交换之后ss 的这块数的数量超过tt 就无解,否则一直交换直至相等。

可以用双指针或队列等实现,时间复杂度线性。

一个预处理技巧:先把字符串用数字表示,比如两个 0 三个 1 四个 0 就是{2,3,4}\lbrace 2,3,4 \rbrace。如果s,ts,t 首位不同,那么ss 必须先交换一次,将s,ts,t 数组统一为第一位表示 0 的个数,第二位是 1 的个数,第三位是 0 的个数,以此类推。

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <bits/stdc++.h>
using ll = long long;
using namespace std;

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

int t;
cin >> t;

while (t--) {
string s, t;
cin >> s >> t;

deque<int> S, T;

// 将 s 转换为 S 数组,记录连续 0/1 的长度
int len = 0;
for (int i = 1; i < s.size(); i++) {
len++;
if (s[i] != s[i - 1]) {
S.push_back(len);
len = 0;
}
}
len++;
S.push_back(len);

S.push_back(0);
S.push_back(0);
S.push_back(0);
S.push_back(0);

// 将 t 转换为 T 数组,记录连续 0/1 的长度
len = 0;
for (int i = 1; i < t.size(); i++) {
len++;
if (t[i] != t[i - 1]) {
T.push_back(len);
len = 0;
}
}
len++;
T.push_back(len);

int res = 0;
bool ok = 1;

// 如果 t 的第一个字符与 s 的第一个字符不同,那么 s 必须先交换一次
if (t[0] != s[0]) {
S[2] += S[0];
S.pop_front();
res++;
}

while (!T.empty()) {
int x = S.front(); S.pop_front();
int y = T.front(); T.pop_front();

while (x < y && S.size() >= 3) {
// 交换 S[0] 和 S[1]
x += S[1];
S[2] += S[0];
S.pop_front();
S.pop_front();
res++;
}

if (x != y || S.empty()) {
ok = 0;
break;
}
}

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

return 0;
}