0%

题目大意

题目描述
Evelyn 和 Todd 正在进行一场游戏。游戏开始时有一个包含 NN 个整数的多重集合 SS,以及一个初始为空的数组 vv。两人轮流进行操作,每回合当前玩家从 SS 中选择一个元素,将其添加到数组 vv 的末尾,并将其从 SS 中移除。

SS 为空时游戏结束。此时计算数组 vv 中的逆序对数量(即满足 i<ji < jvi>vjv_i > v_j 的索引对的数量)。如果逆序对数量为偶数,则 Evelyn 获胜;如果为奇数,则 Todd 获胜。

两人都充分了解游戏并采用最优策略进行。对于给定的多重集合 SS,有以下四种可能的结果:

  • 无论谁先手,Evelyn 必胜(输出 E)。

  • 无论谁先手,Todd 必胜(输出 T)。

  • 无论先手是谁,先手玩家必胜(输出 F)。

  • 无论先手是谁,后手玩家必胜(输出 S)。

输入格式
第一行包含一个整数 NN1N1051 \le N \le 10^5),表示多重集合 SS 中的元素数量。
第二行包含 NN 个整数 S1,S2,,SNS_1, S_2, \ldots, S_N1SiN1 \le S_i \le N),表示多重集合 SS 中的元素。

输出格式
输出一个大写字母,表示在双方最优策略下的游戏结果(E、T、F 或 S)。

样例一

输入

1
2
3
1 1 1

输出

1
E

样例一解释
无论 Evelyn 和 Todd 如何从多重集合 {1,1,1}\{1, 1, 1\} 中选择元素,最终得到的数组都将是 [1,1,1][1, 1, 1]。由于该数组中没有逆序对(数量为 00,是偶数),因此无论谁先手,Evelyn 都将获胜。

样例二

输入

1
2
3
3 1 2

输出

1
S

思路讲解

第 49 届 ICPC区域赛沈阳站——D. Dot Product Game

不过好像其实没什么关系。

当一个数被塞入这个 v 之中的时候, 其实,其对 inv 的贡献已经确定了

image

呃,不过我感觉我们的思路有点问题,我们来看看 gemini 的回答:

image

image

我们发现,所有两两配对的部分,其贡献都为 0。

贡献为 0 的部分,就把它丢掉,少即是好。我们删除的部分,不仅这个贡献为 0,对于博弈论来说,他们两者的操作还是这个互为镜像的。

image

然后,转换成这样了,变成 0/1 的这个奇偶性游戏了,我们会发现,最后一个手握自由改变奇偶性机会的人,就是赢家

这个是因为,无论前面怎么变化,奇偶性要么是 0,要么是 1,我如果手握这个选择,我就可以选对自己有利的,想保持就保持,想改变就改变。

image

我们发现,选择 S 中第一个大的数字(采用 0-based,其实也就是第二大的数字),一定可以改变奇偶,因此,我们会发现,倒数第二个操作的人,就是赢得这个游戏的人,因为其是最后一个手握自由改变奇偶性机会的人

那么答案就非常明显了。

1
2
3
4
5
6
7
8
9
if (odd_freq_cnt <= 1) {
cout << "E\n";
return;
}
if (odd_freq_cnt % 2 == 0) {
cout << "F\n";
}else {
cout << "S\n";
}

AC代码

AC
https://codeforces.com/gym/106416/submission/368226851

心路历程(WA,TLE,MLE……)

题目大意

image

一开始对题面的理解有问题,我以为他可以保留折扣机会,但是实际上不行,不能保留。

题目描述

给定 NN 个顾客的商品购买需求,第 ii 个需求对应的商品价格为偶数 AiA_i

商店有一项优惠活动:每完成 XX 次全价购买后,下一次购买单件商品可以获得 50% 的折扣。此次打折购买不计入下一次获取折扣所需的 XX 次全价购买中。

你可以自由决定这 NN 件商品的购买顺序。但为了满足发货时间限制,第 ii 个需求对应的商品,必须在你的前 i+Ki+K 次购买中完成。

由于顾客已经提前支付了全额费用,你通过折扣节省下来的金额即为你个人的利润。请计算并输出你能获得的最大总利润(即所有打折购买商品原价的一半的总和)。

输入格式

第一行包含三个整数 NNXXKK1N,X2×1051 \le N, X \le 2 \times 10^50K2×1050 \le K \le 2 \times 10^5),分别表示需求数量、获得折扣所需的全价购买次数,以及延迟发货的限制参数。
第二行包含 NN 个偶数 A1,A2,,ANA_1, A_2, \dots, A_N2Ai1092 \le A_i \le 10^9),依次表示每个需求商品的价格。

输出格式

输出一个整数,表示你能获得的最大总利润。

样例 1

输入:

1
2
3 1 0
6 4 14

输出:

1
2

样例 1 解释

由于 K=0K=0,第 ii 个需求必须在第 ii 次购买时完成。因此购买顺序被严格固定为 [6, 4, 14]。只有第二次购买(价格为 44)可以享受半价折扣,能获得的最大利润为 4/2=24 / 2 = 2

样例 2

输入:

1
2
3 1 1
6 4 14

输出:

1
7

样例 2 解释

由于 K=1K=1,第 ii 个需求必须在前 i+1i+1 次购买内完成。合法的购买顺序包括 [6, 4, 14][6, 14, 4][4, 6, 14] 以及 [14, 6, 4]
其中,顺序 [6, 14, 4] 是最优的:在花费全价购买价格为 66 的商品后,可以对价格为 1414 的商品使用折扣,从而获得最大利润 14/2=714 / 2 = 7

思路讲解

呃,这个问他问的太早了。其实知道了,不能够保留这个折扣机会,那么其实倒过来看就非常 easy 的一件事情了

为什么反向处理呢?我们不难发现,一个数,更早被处理完全没有任何问题,但是不能更晚被处理

或者,可以这么说,正过来看,ddl 是 ddl,也就是截止时间,但是反过来看,ddl 是激活时间

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
ll gen_ans(const map<ll, vector<ll> > &ddl_price, ll N, ll X) {
ll ans = 0;
// gen_sorted_st(ddl_price, st1);
multiset<ll> st_small;
multiset<ll,greater<>> st_big;
// gen_sorted_st(ddl_price, st2);
for (int i = N; i >= 1; --i) {
auto it = ddl_price.find(i);
// 有没有 ddl 是 i 的商品?我们对其进行激活
if (it != ddl_price.end()) {
for (auto price: it->second) {
st_big.insert(price);
st_small.insert(price);
}
}
if (i % (X + 1) == 0) {
// 折扣的时候选择大的数字
ll add=*st_big.begin();
ans+=add/2;
st_erase(st_small,st_big,add);
} else {
// 平时抛弃小的数字
ll add=*st_small.begin();
st_erase(st_small,st_big,add);
}
}
return ans;
}

AC代码

AC
https://codeforces.com/gym/106416/submission/368218310

心路历程(WA,TLE,MLE……)

题目大意

题目描述
构造一个长度不超过 500500 且仅由大写字母 CGAT 组成的非空字符串,使其满足以下两个条件:

  1. 字符串中包含恰好 GG 个子序列 GATA(即字符 GATA 按顺序出现,中间可以包含其他字符)。

  2. 字符串中包含恰好 CC 个子序列 CAT(即字符 CAT 按顺序出现,中间可以包含其他字符)。

输入格式
第一行包含一个整数 QQ1Q10001 \le Q \le 1000),表示询问的组数。
接下来的 QQ 行,每行包含两个整数 GGCC0G,C1060 \le G, C \le 10^6),分别表示所需的 GATA 子序列的数量和 CAT 子序列的数量。

输出格式
对于每组询问,输出一行满足条件的字符串(长度不超过 500500 且仅包含 CGAT)。如果存在多个解,输出任意一个即可。题目保证在给定限制下一定有解。

样例输入与输出

样例 1:

1
2
3
4
5
6
7
8
9
10
11
12
Input:
4
1 1
2 3
18 1
2 1

Output:
GATCAT
GGCATAT
GATGGATATCAT
GGATCAT

样例 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Input:
5
1 0
0 1
1 1
0 0
0 0

Output:
GATAT
CAT
GATCAT
GG
GG

样例解释

对于样例 1 的第一组询问(G=1,C=1G=1, C=1),输出为 GATCAT

  • GATA 子序列的数量为 11(由第 1 个字符 G、第 2 个字符 A、第 3 个字符 T、第 5 个字符 A 组成)。

  • CAT 子序列的数量为 11(由第 4 个字符 C、第 5 个字符 A、第 6 个字符 T 组成)。

对于样例 2 的第一组询问(G=1,C=0G=1, C=0),输出为 GATAT

  • GATA 子序列的数量为 11(由第 1 个字符 G、第 2 个字符 A、第 3 个字符 T、第 5 个字符 A 组成)。

  • 由于字符串中完全不包含字符 C,因此 CAT 子序列的数量为 00

思路讲解

我确实也是想到了插入式的这个构造方法,但是确实没他想的这个么全。

不难发现如下规律:

image

我采用了过程式编程,模块化的思想,把不同的功能模块拆成了不同的函数,避免了Spaghetti Code,也就是面条代码。

我了解了一下,这个不算函数式编程,函数式编程的要求非常严格。

那么我这个代码更算是什么呢?

逐步求精(Stepwise Refinement)

这是由计算机科学家尼克劳斯·维尔特(Niklaus Wirth,PASCAL 语言的发明者)在 1971 年提出的。指的是将一个复杂的任务,不断分解成更小、更简单的子任务,直到这些子任务简单到可以用几行代码直接实现为止****。

我相信这个代码还是很清楚的。

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
85
86
87
88
89
90
91
92
93
94
95
96
vector<ll> at_ls, ata_ls;
ll siz_AT_repeat = 140;

void init() {
at_ls.resize(siz_AT_repeat);
ata_ls.resize(siz_AT_repeat);
for (int i = 1; i < siz_AT_repeat; ++i) {
at_ls[i] = i;
ata_ls[i] = i;
}
// 上面是差分数组
// 下面进行一次这个前缀和或者两次前缀和
partial_sum(all(at_ls), at_ls.begin());
partial_sum(all(ata_ls), ata_ls.begin());
partial_sum(all(ata_ls), ata_ls.begin());
#ifdef LOCAL
debug1d(ata_ls);
debug1d(at_ls);
#endif
}

// 贪心地计算需要在哪里插入 G/C
vector<ll> greedy_cal(const vector<ll> &A, ll x) {
vector<ll> res;
while (x != 0) {
auto it = upper_bound(all(A), x);
if (it == A.begin()) {
// #ifdef LOCAL
// debug(x);
// #endif
assert(0);
} else {
--it;
// 应该在倒数第几个 A 前插入?
res.push_back(it - A.begin());
x -= *it;
}
}
#ifdef LOCAL
debug1d(res);
#endif
return res;
}


struct Ch_Num_CG {
char ch;
ll num_g;
ll num_c;
};

void opls_insert(vector<Ch_Num_CG> &ans, vector<ll> opls, ll offset, char cg) {
// assert(is_sorted(all(opls),greater<>()));
sort(all(opls), greater());
ll cnta = 0;
for (int i = SZ(ans) - 1; i > offset; --i) {
if (ans[i].ch == 'A') {
cnta++;
}
while (!opls.empty() && opls.back() == cnta) {
if (cg == 'G') {
ans[i - offset].num_g++;
} else {
ans[i - offset].num_c++;
}
opls.pop_back();
}
}
}

string gen_ans(const vector<Ch_Num_CG> &ans) {
string res;
for (auto [ch,num_g,num_c]: ans) {
res += string(num_g, 'G');
res += string(num_c, 'C');
res += ch;
}
return res;
}

void Solve() {
ll G, C;
cin >> G >> C;
vector<ll> g_opls = greedy_cal(ata_ls, G);
vector<ll> c_opls = greedy_cal(at_ls, C);
string s;
vector<Ch_Num_CG> Ans;
for (int i = 0; i < siz_AT_repeat + 5; ++i) {
Ans.push_back({'A', 0, 0});
Ans.push_back({'T', 0, 0});
}
opls_insert(Ans,g_opls,2,'G');
opls_insert(Ans,c_opls,0,'C');
cout << gen_ans(Ans) << "\n";
}

AC代码

AC
https://codeforces.com/gym/106416/submission/368168341

心路历程(WA,TLE,MLE……)

题目大意

image

一开始没注意到这句话,绷不住了。注意,读题面,一定要把每句话都读到,你就看大写字母就完了

也不能怪我,这个 if 在 cf 题面上的这个位置太阴了。

image

题意总结

给定 NN 个球,以及它们被依次使用时的颜色序列 K1,K2,,KNK_1,K_2,\dots,K_N

Antonella 按如下规则逐个放球,且每一步之后都必须保持整个球堆稳定:

image

  1. 第一个球一定放在地面上。

  2. 之后每次加入一个新球时,这个球要么放在地面上,要么恰好放在两个球的正上方。

  3. 所有在地面上的球必须紧密排列,中间不能有空隙。

  4. 如果当前有多个合法放置位置,Antonella 一定选择最高的位置。

  5. 如果最高的位置不止一个,则她可以任选其中一个。

若若干个同色球两两之间可以通过“接触”的球一路连通,则它们构成一个同色连通块,称为一个 cluster。cluster 的大小就是其中球的个数。

题目要求你在所有可能构造出的稳定球堆中,求出:

所有颜色的所有 cluster 里,最大 cluster 的大小 的最大可能值。

也就是说,要输出 Antonella 在满足规则的前提下,最终有可能得到的最大同色连通块大小。

输入格式

第一行是整数 NN

第二行是 NN 个整数 K1,K2,,KNK_1,K_2,\dots,K_N,表示每个球按加入顺序的颜色。

输出格式

输出一个整数,表示答案。

样例 1

1
2
3
4
5
6
Input
3
1 2 1

Output
2

解释:

先放第一个颜色为 1 的球。第二个颜色为 2 的球也只能放在地面上,可以放在第一个球左边或右边。第三个颜色为 1 的球此时会放到前两个球上方。这样最终颜色为 1 的两个球会连成一个大小为 2 的 cluster,所以答案是 2

样例 2

1
2
3
4
5
6
Input
3
1 1 1

Output
3

解释:

三个球颜色都相同,不管具体怎么放,最终都能连成一个大小为 3 的同色 cluster,所以答案是 3

样例 3

1
2
3
4
5
6
Input
4
1 5 5 1

Output
2

解释:

按规则逐步加入后,最大的同色连通块大小最多只能达到 2,因此答案是 2

样例 4

1
2
3
4
5
6
Input
6
1 2 2 1 2 3

Output
3

解释:

在某种合法构造下,颜色为 2 的球可以形成一个大小为 3 的 cluster,因此答案是 3

思路讲解

这道题目不是很想补,毕竟暴力嘛,算了。

AC代码

心路历程(WA,TLE,MLE……)

题目大意

题目描述
在一个圆上有 NN 只蚂蚁。圆被顺时针划分为 LL 个位置,编号为 11LL,其中位置 LL 与位置 11 相邻。
每只蚂蚁初始站在不同的位置上,并且都需要到达各自不同的目标位置。
在每一秒内,每只蚂蚁可以选择:

  • 停留在原地。

  • 顺时针或逆时针移动到相邻的位置。

在任何时刻(包括在两个位置之间移动的过程中),任意两只蚂蚁都不能处于同一个位置。例如,如果某只蚂蚁在某一秒内顺时针从位置 11 移动到位置 22,那么在同一秒内,其他蚂蚁不能进行以下操作:

  • 停留在位置 22(这会导致两只蚂蚁在位置 22 相遇)。

  • 逆时针从位置 33 移动到位置 22(这会导致两只蚂蚁在位置 22 相遇)。

  • 逆时针从位置 22 移动到位置 11(这会导致两只蚂蚁在位置 11 和位置 22 之间相遇)。

你需要判断是否有可能让所有蚂蚁都到达各自的目标位置。如果可以,求出所需的最短时间(秒数)。

输入格式
第一行包含两个整数 NN1N10001 \le N \le 1000)和 LL1L1091 \le L \le 10^9),分别表示蚂蚁的数量和位置的总数。
第二行包含 NN 个互不相同的整数 A1,A2,,ANA_1, A_2, \dots, A_N1AiL1 \le A_i \le L),表示每只蚂蚁的初始位置。
第三行包含 NN 个互不相同的整数 B1,B2,,BNB_1, B_2, \dots, B_N1BiL1 \le B_i \le L),表示每只蚂蚁的目标位置。

输出格式
输出一行,包含一个整数,表示所有蚂蚁到达目标位置所需的最短时间。如果无论如何都无法全部到达目标位置,则输出星号 *

样例展示与解释

样例 1
输入:

1
2
3
2 2
2 1
2 1

输出:

1
0

样例 1 解释:两只蚂蚁初始就已经在各自的目标位置上,因此需要 00 秒。

样例 2
输入:

1
2
3
2 2
1 2
2 1

输出:

1
1

样例 2 解释:两只蚂蚁可以同时顺时针(或同时逆时针)移动,仅需 11 秒即可到达目标位置。

样例 3
输入:

1
2
3
1 10
1
7

输出:

1
4

样例 4
输入:

1
2
3
3 5
1 3 2
5 2 4

输出:

1
*

样例 5
输入:

1
2
3
3 5
1 3 2
4 2 5

输出:

1
2

样例 5 解释:三只蚂蚁都可以选择逆时针移动,其中第二只蚂蚁需要在中途停留一秒。

思路讲解

image

这道题目,实际上可以 O(n)O(n) 解决(忽略了快排的时间复杂度),至少我们的做法是这个时间复杂度的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sort(all(lr_global));
for (int i = 0; i < SZ(lr_global); ++i) {
if (lr_global[0].r > lr_global[i].r) {
lr_global[i].r += L;
}
}
auto check = [&]() -> bool {
for (int i = 1; i < N; ++i) {
if (lr_global[i].l == lr_global[i - 1].l) {
return false;
}
}
for (int i = 1; i < N; ++i) {
if (lr_global[i].r <= lr_global[i - 1].r) {
return false;
}
}
return true;
};
if (!check()) {
cout << "*\n";
return;
}

核心代码就这一段,只能说化曲为直 yyds。

1
2
3
4
5
6
7
for (ll shift: {-1, 0, 1}) {
ll ans = 0;
for (auto [l,r]: lr_global) {
ans = max(abs(l + L * shift - r), ans);
}
ans_global = min(ans_global, ans);
}

为什么这个代码对呢?其实在化曲为直下,一切都非常简单。

说白了,我们只要 l 的相对位置不变,r 的相对位置不变,那么就一定有解,是不是?

那么,我们知道,一个点,他切换方向,其实就是 l 在化曲为直的线段上,平移了 Len 位如果其他点不也平移的 L 位的话,这个一定会导致错误的发生(即 l 的相对位置发生变化)!

那么我们就知道了,要切换方向,要么一起切换,要么一起不切换

当然,你会说,这很奇怪啊,明明这个有些例子是有些点逆时针,有些点顺时针啊?其实,这个是因为为了符合我们的第一个条件,就是起始点和结束点必须都保持升序的条件,在保持这个条件的情况下,其实方向的变化是一起的。

AC代码

AC
http://codeforces.com/gym/106416/submission/368041079

心路历程(WA,TLE,MLE……)