Codeforces Global Round 10 [A - F]
Codeforces Global Round 10 [A - F]
A. Omkar and Password
题目大意
给定一个长度为\(n\)的序列\(a\),每次你可以从中选择相邻但不相等的两个元素\(a_i\),\(a_{i+1}\),将这两个数进行合并且替换为\(a_i + a_{i+1}\)。例如 \([7,4,3,7] \rightarrow [7,4+3,7]\),直到无法继续进行这样的操作,最后返回最终序列的长度。
1 <= n <= 2*10^5
1 <= ai <= 10^9
考点: greedy
math
*800
解题思路
通过思考我们可以发现,除非序列全为相同的数字,否则我们一定可以将最后的序列长度压缩至\(1\)。
因此可以写出如下代码:
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
int f[maxn];
void solve(){
int n; cin >> n;
for (int i = 0; i < n; ++ i) cin >> f[i];
sort(f, f + n);
if (f[0] == f[n - 1]) cout << n << '\n';
else cout << "1\n";
}
int main(){
int t; cin >> t;
while (t--)
solve();
return 0;
}
B. Omkar and Infinity Clock
题目大意
给定一个长度为\(n\)的序列\(a\),对其进行\(k\)次操作,每次操作为:
- 取出当前序列的最大值\(d\)、
- 对于序列中的每个数\(a_i\),将其变为\(d - a_i\)。
返回进行\(k\)次操作之后序列的情况。
1 <= n <= 2*10^5
1 <= k <=10^18
考点: implementation
math
*800
解题思路
在进行第一次操作后,序列中的最大值和最小值已经固定,其中最大值为:\(\max{arr} - \min{arr}\),最小值为\(0\),之后再进行操作实际上是一种周期性的重复。
(小tips:在看到\(k\)的数据范围时,可以猜测这题是一个周期性问题!)
代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int maxn = 2e5 + 50;
int f[maxn];
int ans[maxn][2];
int main(){
int t; cin >> t;
while (t--){
int n;
LL k;
cin >> n >> k;
int mn = 0x3f3f3f3f, mx = 0xc0c0c0c0;
for (int i = 0; i < n; ++ i){
cin >> f[i];
mn = min(mn, f[i]);
mx = max(mx, f[i]);
}
for (int i = 0; i < n; ++ i) ans[i][0] = mx - f[i];
for (int i = 0; i < n; ++ i) ans[i][1] = (mx - mn) - ans[i][0];
if (k & 1){
for (int i = 0; i < n; ++ i) cout << ans[i][0] << (i == n - 1 ? '\n' : ' ');
}else {
for (int i = 0; i < n; ++ i) cout << ans[i][1] << (i == n - 1 ? '\n' : ' ');
}
}
return 0;
}
C. Omkar and Waterslide
题目大意
给定一个长度为 \(n\) 的数组 \(a\) ,每次可以选取一个非降序列,使得序列中每个值增加一。问最少操作多少次使得整个数组 非降
(\(1 \leq n \leq 2 * 10^5\))
(\(0 \leq a_i \leq 10^9\))
greedy
implementation
*1200
解题思路
分析后可以发现,只需要考虑谷底值。由于要求整个序列非降,因此我们只需要考虑\(a_i\)与\(a_{i - 1}\)的关系。(左值)
- 当碰到 \(a_i \leq a_{i - 1}\) 时需要进行操作,将\(a_i \rightarrow a_{i + 1}\)。
- 当碰到 \(a_i \ge a_{i - 1}\) 时,则不需要考虑。
通俗的话来讲,每次你只需要考虑能否满足当前值\(a_i\)大于等于前一个值\(a_{i - 1}\)。
代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int maxn = 2e5 + 50;
int f[maxn];
void solve(){
int n; cin >> n;
for (int i = 0; i < n; ++ i) cin >> f[i];
LL ans = 0;
for (int i = 0; i + 1 < n; ++ i){
ans += max(f[i] - f[i + 1], 0); // 只需要考虑左值
}
cout << ans << '\n';
}
int main(){
int t; cin >> t;
while (t--)
solve();
return 0;
}
D. Omkar and Bed Wars
题目大意
从 \(1\) 到 \(n\)围成一个圈, 每个人可以选择攻击左边(
L
)的人,或者右边(R
)的人。且需要满足如何规则:
- 当只被一个人攻击时,必须攻击这个人。
- 当没有被攻击或者被两个人攻击时,可以攻击身边的任意一个人。
给定一个长度为 \(n\) 的序列代表攻击情况,问最少需要修改几次序列能使其满足规则。
(\(3 \leq n \leq 2*10^5\))
考点:constructive algorithms
dp
greedy
string suffix structures
*1700
解题思路
经过分析,可以发现只要存在LLL
,RRR
都是不合理的。要进行替换。也就是将其中某个位置的字符改变。
之后,需要考虑如何对于这种LLL...LLL
序列进行处理能使得到的结果最优。
RRLRR
改变一个字符,最多能处理长度为 5 的重复串RRLRRLRR
改变两个字符,最多处理长度为 8 的重复串
然后我们可以得出结论:对于长度为 \(n,n \ge 3\) 的重复串(注意,这里我们没有考虑首尾连接的情况),我们只需要改变 \(\frac{n}{3}\) 次
现在考虑首位连接的情况,当首尾连接时,长度为 \(n,n \ge 3\) 的重复串需要进行几次操作,由于首尾相连,因此首端和尾端能放置的字符从原来的 2 个,到现在的最多 1 个。也就是从RRLRR
改变为 RLR
。因此我们只需要改变 \(\frac{n + 2}{3}\) 次。(逆向思维,认为我们重复串的长度为 \(n + 2\))。
在书写代码的时候,可以首先将尾部与首部相同的字符去除,并修改计数器(相当于将其转移到首部)。之后判断是否整个序列为相同字符,若整个序列相同则特殊处理。
代码
#include <bits/stdc++.h>
using namespace std;
void solve(){
int n; cin >> n;
string ss; cin >> ss;
int cnt = 0, ans = 0;
// 将尾部转移到首部(通过修改 cnt 完成)
while (!ss.empty() && ss[0] == ss.back()){
++ cnt;
ss.pop_back();
}
if (ss.empty()){
if (cnt <= 2){
cout << 0 << '\n';
return;
}
if (cnt == 3){
cout << 1 << '\n';
return;
}
cout << (cnt + 2) / 3 << '\n';
return;
}
ss += '$'; // 添加一个字符,保证能全部处理完成
for (int i = 0; i + 1< ss.size(); ++ i){
++ cnt;
if (ss[i] != ss[i + 1]){
ans += (cnt / 3);
cnt = 0;
}
}
cout << ans << '\n';
}
int main(){
int t; cin >> t;
while (t--)
solve();
return 0;
}
后记
该题还有 dp
解法,后面补上
E. Omkar and Duck
题目大意
给定一个 \(n\) ,生成一个\(n \times n\)的矩阵。要求给定一个\(k\)值,输出唯一确定的路径 \((1, 1) \rightarrow (n, n)\) 如此。
bitmasks
constructive algorithms
math
*2100
解题思路
新知识点补充(这题还不够熟练):
- 面对需要确定唯一路径时,可以想到2的幂,也就是通过二进制 \(2^n\) ,来构造一个唯一确定的序列
代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
int n; cin >> n;
vector<vector<LL> > mat(n + 1, vector<LL>(n + 1 , 0));
for (int i = 1; i <= n; ++ i){
for (int j = 1; j <= n; ++ j){
if (i & 1) cout << "0 ";
else cout << (1LL << (i + j)) << ' ';
}
cout << endl;
}
cout.flush();
int q; cin >> q;
while (q--){
LL sum; cin >> sum;
cout << "1 1\n";
int row = 1, col = 1;
for (int k = 1; k <= 2 * n - 2; ++ k){
int cur = col + row;
if (row & 1){
if (sum & (1LL << (cur + 1))) ++ row;
else ++ col;
}else{
if (sum & (1LL << (cur + 1))) ++ col;
else ++ row;
}
cout << row << " " << col << endl;
}
}
cout.flush();
return 0;
}
F. Omkar and Landslide
题目大意
给定一个长度为 \(n\) 的升序序列 \(H\) ,任意时刻序列中存在 \(h_i + 2 \leq h_{i + 1}\) 时,发生“滑坡”,即\(h_i\) 加一, \(h_{i +1}\) 减一。且所有滑坡同时进行。请问最后序列\(H\)的最终状态。
\(1 \leq n \leq 10^6\)
\(0 \leq hi \leq h_{i+1} \leq 10^{12} \quad \forall \;i \in [1, n]\)
constructive algorithms
data structures
greedy
math
*2400
解题思路
(这部分为特殊思路:看到 \(n\) 的规模以及最终时刻这两个字眼,我心里就想到了这题是一个数学
,贪心
,构造
题。后面补题的时候发现果然是的)
F题的解题核心在于得到最终状态的条件。考虑到实际上只需要判断四个点就可以模拟滑坡的过程,我们选取\(a_{i - 1}, a_{i}, a_{i + 1}, a_{i + 2}\)进行考虑。我们假设\(a_{i},a{i - 1}\)之间;\(a_{i + 1}, a_{i + 2}\)之间不会进行滑坡,意味着\(a_{i + 2} - a_{i + 1} \leq 1\)(0, 1两种状态)。设当前\(a_{i + 1} - a{i} == 2\),进行滑坡之后就会出现\(a_{i} == a_{i + 1}\)。而由于\(a_{i + 1}\)减少了一,所以\(a_{i + 2} - a_{i + 1} \ge 1\)所以,每个新的等式出现一定会破坏原有的一个等式
整个序列中最终只会出现最多一对相等的元素
我们可以通过贪心构造出一个递增的数列,将剩下未分配的值贪心分配给前几个元素。
代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
inline LL read(){
/* 注意假如为 long long 时需要修改 */
char c = getchar();
LL x = 0, f = 1;
while (!isdigit(c)) { if (c == '-') f = -1; c = getchar(); }
while (isdigit(c)) x = (x << 3) + (x << 1) + (c^48), c = getchar();
return f * x;
}
const int maxn = 1e6 + 50;
LL a[maxn];
int main(){
LL n; n = read();
LL sum = 0;
for (int i = 0; i < n; ++ i) a[i] = read(), sum += a[i];
sum -= (n * (n + 1)) / 2;
LL eve, lvf;
eve = sum / n;
lvf = sum % n;
for (int i = 0; i < n; ++ i){
cout << (i + 1) + eve + (i + 1 <= lvf) << (i == n ? '\n' : ' ');
}
return 0;
}
后记
这个题目的重点在于最终状态的寻找,牢记贪心算法总是与数学规律挂钩(尤其是构造)