Codeforces Div2 821

Codeforces Round #821 (Div. 2)

A.Consecutive Sum

题目大意

给定一个长度为$n$的数组$a$,可做以下操作至多$k$次:
选择两个下标$i$和$j$,要求$i\ mod\ k=j\ mod\ k$,然后交换$a_i$和$a_j$
操作完后,任意选连续$k$个数,这些数的的和是你的分数,问分数最多是多少

解题思路

签到题
相当于把数组中的所有元素按除以$k$得到的余数分组,在每个组中取最大值加起来就可以

具体代码

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
#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <vector>
typedef long long LL;
const LL N = 2e5 + 10;
const LL MOD = 1e9 + 7;
int n, m, q;
LL a[N];
LL c[N];
LL ans;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--)
{
memset(c, 0, sizeof c);
ans = 0LL;
std::cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
std::cin >> a[i];
c[i % m] = std::max(c[i % m], a[i]);
}
for (int i = 0; i < m; ++i)
ans += c[i];
std::cout << ans << '\n';
}
return 0;
}

B.Rule of League

题目大意

有一场羽毛球比赛,$n$个玩家参与
比赛是这样进行的:先让玩家$1$和玩家$2$比,胜者和玩家$3$比,胜者和玩家$4$比,$\cdots$,一直这样比$n-1$轮
现在给出$x$和$y$,代表比赛结束后每名玩家的的胜场数要么是$x$要么是$y$
问比赛结果是否有可能满足$x$和$y$,如果能,输出可能的每轮的胜者

解题思路

$x$和$y$不能都大于$0$(考虑第一场比赛,玩家$1$和玩家$2$肯定有一个人以$0$胜场被淘汰)
同时显然$x$和$y$不能都等于$0$
那么就是说$x$和$y$中有一个是$0$,另一个大于$0$
若有$n$名玩家,比赛会进行$n-1$轮
那么只要$n-1$是$max(x,y)$的倍数就可以了

具体代码

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 <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <vector>
typedef long long LL;
const LL N = 2e5 + 10;
const LL MOD = 1e9 + 7;
int n, m, q, x, y;
int a[N];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--)
{
std::cin >> n >> x >> y;
if (!x && !y)
std::cout << -1 << '\n';
else if (x && y)
std::cout << -1 << '\n';
else
{
int t = std::max(x, y);
int rounds = n - 1;
if (!(rounds % t))
{
for (int i = 2; i <= n; i += t)
for (int j = 1; j <= t; ++j)
std::cout << i << ' ';
std::cout << '\n';
}
else
std::cout << -1 << '\n';
}
}
return 0;
}

C.Parity Shuffle Sorting

题目大意

给定长度为$n$的非负数组$a$,可执行下述操作若干次:
第一步:选择两个下标$l$和$r$
第二步:若$a_l+a_r$是奇数,那么令$a_r=a_l$,若$a_l+a_r$是偶数,那么令$a_l=a_r$
找到一种不超过$n$次的操作序列并输出(可证明一定存在这样的操作序列),让$a$数组变成严格不下降序列,注意没有必要让操作次数尽可能小

解题思路

题干里特意强调了限制操作次数不超过$n$次,并且没有必要让操作次数尽可能小
不禁令人想到是不是正解的操作次数就是差不多$n$次
事实也确实如此,只要将序列变成常数列就可以了
操作次数最坏$n-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
62
63
#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <vector>
typedef long long LL;
const LL N = 2e5 + 10;
const LL MOD = 1e9 + 7;
LL n, m, q, t;
LL a[N];
LL l[N], r[N];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--)
{
m = 0;
std::cin >> n;
for (int i = 1; i <= n; ++i)
std::cin >> a[i];
//变成常数列a[i]=t
if ((a[1] - a[n]) & 1) //异奇偶
// 1次
t = a[n] = a[1], l[++m] = 1, r[m] = n;
else
t = a[1] = a[n], l[++m] = 1, r[m] = n;
if (t & 1)
for (int i = 2; i <= n - 1; ++i) // n-2次
{
if (a[i] & 1)
a[i] = a[n], l[++m] = i, r[m] = n;
else
a[i] = a[1], l[++m] = 1, r[m] = i;
}
else
for (int i = 2; i <= n - 1; ++i) // n-2次
{
if (a[i] & 1)
a[i] = a[1], l[++m] = 1, r[m] = i;
else
a[i] = a[n], l[++m] = i, r[m] = n;
}
/*
for (int i = 1; i <= n; ++i)
std::cout << a[i] << ' ';
std::cout << '\n';
*/
if (n == 1)
std::cout << 0 << '\n';
else
{
std::cout << m << '\n';
for (int i = 1; i <= m; ++i)
std::cout << l[i] << ' ' << r[i] << '\n';
}
}
return 0;
}

D1.Zero-One (Easy Version)

题目大意

给了两个长度为$n$的$01$串$a$和$b$,可做以下操作任意次:
第一步:选择两个下标$l$和$r$
第二步:$a_l=1-a_l$,$a_r=1-a_r$(即两个都翻转)
每次操作都是有代价的,如果$r-l=1$,代价为$x$,否则代价为$y$
$Easy\ Version$保证$x\geq y$
输出让$a$变得和$b$一样的最小代价,若变不成一样输出$-1$

解题思路

先把$a$中需要翻转的下标都找出来,记$t$为需要翻转的个数
如果$t$为奇数,那么显然输出$-1$
下面考虑$t$为偶数:
因为保证了$x\geq y$,所以当$t\geq 4$时,肯定是都执行代价为$y$的操作(贪心)(这样的操作是一定存在的)
若$t=2$,如果需要翻转的下标不是连着的,还是执行$y$。如果是连着的,比较$x$和$y\times 2$(随便取第三个下标陪这两个下标执行两次)的大小,执行那个小的操作

具体代码

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
#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <vector>
typedef long long LL;
const LL N = 2e5 + 10;
const LL MOD = 1e9 + 7;
LL n, m, q, x, y, t;
std::string a, b;
bool st[N];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--)
{
t = 0;
std::cin >> n >> x >> y >> a >> b;
bool flag = false;
for (int i = 1; i <= n; ++i)
{
st[i] = a[i - 1] != b[i - 1];
if (st[i] && st[i - 1])
flag = true;
}
for (int i = 1; i <= n; ++i)
t += (LL)st[i];
if (t % 2 == 0)
{
if (flag && t == 2)
std::cout << std::min(y << 1, x) << '\n';
else
std::cout << (t * y >> 1) << '\n';
}
else
std::cout << -1 << '\n';
}
return 0;
}

D2.Zero-One (Hard Version)

题目大意

$n$的数据范围从$3000$变成$5000$,$x$和$y$的大小关系不再有限制
别的和$Easy\ Version$一样

解题思路

当$x\lt y$时,不能再贪心了
于是自然想到动态规划
$dp[l][r]$表示处理第$l$个到第$r$个需要翻转的位置的最小代价

具体代码

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
#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <vector>
typedef long long LL;
const LL N = 5010;
const LL MOD = 1e9 + 7;
const LL INF = 1e18;
LL n, m, q, x, y, t;
char a[N], b[N];
LL dp[N][N]; // dp[l][r]表示把从第l个到第r个需要翻转的位置全部翻转好所需的最小代价
std::vector<int> pos; //需要翻转的下标
LL get(int l, int r)
{
if (l + 1 == r)
return std::min(2 * y, x); // min(找一个第三者执行两次y,直接执行x)
else
return std::min(y, x * (r - l)); // min(直接执行y,一个邻居一个邻居这样翻过去)
}
LL dfs(int l, int r)
{
if (l > r)
return 0;
if (dp[l][r] != -1) //记忆化
return dp[l][r];
LL res = INF;
res = std::min(res, dfs(l + 1, r - 1) + get(pos[l], pos[r]));
res = std::min(res, dfs(l, r - 2) + get(pos[r - 1], pos[r]));
res = std::min(res, dfs(l + 2, r) + get(pos[l], pos[l + 1]));
return dp[l][r] = res;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--)
{
std::cin >> n >> x >> y >> a + 1 >> b + 1;
pos.clear();
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= n; ++j)
dp[i][j] = -1;
for (int i = 1; i <= n; ++i)
if (a[i] != b[i])
pos.push_back(i);
if (pos.size() & 1)
std::cout << -1 << '\n';
else if (pos.size() == 0)
std::cout << 0 << '\n';
else if (pos.size() == 2)
{
if (pos[1] - pos[0] == 1)
std::cout << std::min(y * 2, x) << '\n';
else
std::cout << std::min(y, x * (pos[1] - pos[0])) << '\n';
}
else
{
if (x >= y)
std::cout << pos.size() / 2 * y << '\n';
else // Hard Version(x < y)
std::cout << dfs(0, pos.size() - 1) << '\n';
}
}
return 0;
}

E.Conveyor

题目大意

有一个网格。每个格子上都有传送带(初始方向均为向右),每一秒,都会往格子$(0,0)$上放一个箱子。每一秒,对于所有的格子,如果其上面有箱子的话,则会将箱子移动到相应的方向。并且传送带的方向会改变(向右->向下->向右$\cdots$)
现在给出$t,x,y$,问第$t$秒的时候$(x,y)$这个格子上是否有箱子(从第$0$秒开始,且第$0$秒$(0,0)$已经有箱子了)

解题思路

知道做法后没有想到那么简单,属实是思维题了
记$a[i][j]$为t秒下,$(i,j)$这个格子上途径了多少个箱子
对于一个位于$(i,j)$的箱子,会往$(i,j+1)$送去$a[i][j]/2$上取整个箱子,往$(i+1,j)$送去$a[i][j]/2$下取整个箱子
注意对$a[0][0]$赋初值时,

1
a[0][0] = std::max(t - (x + y) + 1LL, 0LL);

这是因为一个箱子到$(x,y)$也需要时间,达不到这些时间的箱子对当前情况是没有用的

具体代码

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 <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <vector>
typedef long long LL;
const LL N = 150;
const LL MOD = 1e9 + 7;
const LL INF = 0x7f7f7f7f7f7f7f7f;
LL n, m, q, x, y, t;
LL a[N][N]; // a[i][j]为t秒下,(i,j)这个格子上途径了多少个箱子
int cal(int t, int x, int y)
{
memset(a, 0, sizeof a);
a[0][0] = std::max(t - (x + y) + 1LL, 0LL);
for (int i = 0; i <= x; ++i)
for (int j = 0; j <= y; ++j)
{
a[i + 1][j] += a[i][j] / 2;
a[i][j + 1] += (a[i][j] + 1) / 2;
}
return a[x][y];
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--)
{
std::cin >> t >> x >> y;
if (cal(t, x, y) > cal(t - 1, x, y))
std::cout << "YES" << '\n';
else
std::cout << "NO" << '\n';
}
return 0;
}