Codeforces Round #687
写在前面
Codeforces Round #687 (Div. 2, based on Technocup 2021 Elimination Round 2)
比赛地址:http://codeforces.com/contest/1457。
其实 luckyblock233
并没有打这一场,因为最后 10s 没报上名(
瞎口胡了个 D 的二分答案 + 线段树上二分发现单调性假了= =
被 eee_hoho
嘲笑力= =
A
Link。
\(t\) 组数据,每次给定参数 \(n,m,r,c\),求\(n\times m\) 的矩阵中所有位置中到达给定位置 \((r,c)\) 的曼哈顿距离的最大值。
\(1\le t\le 10^4\),\(1\le r\le n\le 10^9\),\(1\le c\le m\le 10^9\)。
1S,256MB。
水题。
距离最远的点一定在四个角上,输出 \(\max(r - 1, n - r) + \max(c - 1, m - c)\)。
//知识点:暴力
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
//=============================================================
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
//=============================================================
int main() {
int t = read();
while (t --) {
int n = read(), m = read(), a = read(), b = read();
printf("%d\n", std::max(a - 1, n - a) + std::max(b - 1, m - b));
}
return 0;
}
B
Link。
\(t\) 组数据,每次给定一长度为 \(n\) 的数列 \(a\),参数 \(k\)。
每次操作可以将一段长度不大于 \(k\) 的区间的任意位置变为任意数。
求使得整个数列全部相等的最小操作次数。
\(1\le t\le 10^4\),\(1\le k\le n\le 10^5\),\(1\le a_1\le 100\),\(\sum n\le 10^5\)。
1S,256MB。
暴力。
暴力枚举最后数列的形态,贪心修改即可。
//知识点:暴力
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, m, ans, a[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
//=============================================================
int main() {
int t = read();
while (t --) {
n = read(), m = read();
ans = kN;
for (int i = 1; i <= n; ++ i) a[i] = read();
for (int i = 1; i <= 100; ++ i) {
int sum = 0;
for (int j = 1; j <= n; ++ j) {
if (a[j] != i) {
sum ++;
j = j + m -1;
}
}
Chkmin(ans, sum);
}
printf("%d\n", ans);
}
return 0;
}
C
Link。
\(t\) 组数据,每次给定一长度为 \(n\) 的 01 序列,参数 \(p,k,x,y\),有两种操作:
- 花费 \(x\) 的代价,使得序列中任意一个 0 变为 1。
- 花费 \(y\) 的代价,删除序列第一个元素,序列长度 \(- 1\),第 \(2\sim n\) 个元素向前移动一位。
求使得 \(\forall q\in \mathbb{Z},\ p+qk\le n,\ a_{p+qk} = 1\) 的最小代价。
\(1\le t\le 100\),\(1\le p\le n\le 10^5\),\(1\le k\le n\),\(\sum n\le 10^5\)。
1S,256MB。
DP。
猜俩结论,显然操作 1 仅会修改对答案有贡献的位置。且操作的顺序一定是若干次操作 2 加上若干次操作 1。
第二个结论正确性显然,若先进行一次操作 1 再进行操作 2,之前修改的 1 可能会移动到对答案没有贡献的位置上。不如先进行操作 2,再仅修改对答案有贡献的位置。
数据范围不大,考虑枚举操作 2 进行的次数 \(i\),新的对答案有贡献的位置为初始序列的 \((p + i) + qk\)。
发现步长没有改变,考虑预处理 \(f_{j}\) 表示 \((p+i) = j\) 时,有贡献位置上 \(0\) 的个数,显然有:
则操作 2 进行 \(i\) 次的最小代价为 \(f_{p+i}\times x + i\times y\),取最小值即为答案。
总时间复杂度 \(O(n)\)。
//知识点:枚举,DP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
const int kInf = 1e9 + 2077;
//=============================================================
int ans, f[kN];
char s[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
//=============================================================
int main() {
int t = read();
while (t --) {
ans = kInf;
int n = read(), p = read(), k = read();
scanf("%s", s + 1);
int x = read(), y = read();
for (int i = n; i >= 1; -- i) {
if (i + k > n) {
f[i] = (s[i] == '0');
} else {
f[i] = f[i + k] + (s[i] == '0');
}
}
for (int i = 0; i <= n - p; ++ i) {
Chkmin(ans, i * y + f[i + p] * x);
}
printf("%d\n", ans);
}
return 0;
}
D
Link。
给定一长度为 \(n\) 的 单调不降 的数列 \(a\),每次操作可以取出相邻的两个数,并将它们的异或值插入原位置。
求使得该数列 不单调不降 的最小操作次数。
\(1\le n\le 10^5\),\(1\le a_i\le 10^9\)。
2S,256MB。
结论,暴力。
有一些显然的性质。
- 使得该数列 不单调不降,即存在合并后的两个位置,使得前一个 大于 后一个,以下称它为断点。
- 被操作的位置一定是包含上述断点的 一段 区间。断点前的部分构成较大的数,断点后的部分构成较小的数。
正确性显然。为了保证代价最小,断点不可能存在两个或两个以上。为使断点位置满足条件,需要对它前后分别操作。
于是可以考虑暴力枚举断点以及左右的区间,求得最小代价,复杂度 \(O(n^3)\)。
再观察题目的特殊性质,从位运算的角度考虑,可以发现:
若有三个连续的数的最高位相同,则可将后面两个异或起来消去最高位,使得第一个数大于后面的数。仅需操作 1 次即可。
又数列单调不降,则最长的使得三个连续的数最高位不同的数列长度为 \(2\times 30\)。
特判 \(n>60\) 时输出 1 即可通过本题。
总时间复杂度 \(O(60^3)\)。
//知识点:结论
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 60 + 10;
//=============================================================
int n, ans = kN, a[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
//=============================================================
int main() {
n = read();
if (n > 60) {
printf("1\n");
return 0;
}
for (int i = 1; i <= n; ++ i) {
a[i] = a[i - 1] ^ read();
}
for (int l = 1; l < n - 1; ++ l) {
for (int r = l; r < n; ++ r) {
for (int k = r + 1; k <= n; ++ k) {
if ((a[r] ^ a[l - 1]) > (a[k] ^ a[r])) {
Chkmin(ans, (r - l) + (k - r - 1));
}
}
}
}
printf("%d\n", (ans == kN) ? -1 : ans);
return 0;
}
E
给定 \(n\) 个数 \(a_1\sim a_n\)。有一个变量 \(x\),其初始值为 \(0\)。
每次操作可以选择一个数 \(a_i\) 删除,并得到 \(x\) 的价值。之后令 \(x\) 变为 \(x+a_i\)。
特别地,有 \(k\) 次在删除一个数 \(a_i\) 前令 \(x\) 清空的机会。
最大化得到的价值之和。
\(1\le n\le 5\times 10^5\),\(0\le k\le 5\times 10^5\),\(|a_i|\le 10^6\)。
2S,256MB。
贪心。
考虑连续删除一段长度为 \(i + 1\) 的序列 \(a_{p_i}, a_{p_{i-1}},\cdots,a_{p_{0}}\),得到的价值之和为:
则问题等价于将所有数排成一个序列,并划分为不多于 \(k + 1\) 个有序段,每段的价值为 \(\sum_{i=0} i\times a_{p_{i}}\),求最大价值和。
对于这些有序段,可以发现一些结论:
- 贪心地想,需要让较大的数贡献次数较多,则这些有序段一定是 不递减 的。
- 所有 \(a_i\ge 0\) 一定都 在同一段 中。
正确性显然,若存在两 \(a_i\ge 0\) 不在同一段中,则可将它们合并,使其中一个的贡献次数增加。 - 所有 \(a_i \ge 0\) 所在的一段一定是最长的一段。
正确性也显然,若这些数所在的段不是最长的,则最长的段一定全是 \(a_i <0\),可以将最长段后面的适量元素移到 \(a_i \ge 0\) 的前面,使得 \(a_i\ge 0\) 贡献次数增加,且被调整的元素贡献次数不增。 - 最长段外的段的长度差最大为 1。
最长段外的段中仅有 \(a_i < 0\),若它们之间的长度差 \(> 1\),则可以将较长段后面的适量元素扔到较短段后面,使被调整元素贡献次数减少,从而增加权值和。
根据上述结论,考虑贪心地分配这 \(k+1\) 个段。
先将所有数升序排序,预处理一个后缀和。每次用后缀和、记录的每一段的权值和得到将剩余的未分配部分接到最短段后面 的价值和。再将最小的未分配的数接到最短段后面,更新该段的价值。
总复杂度 \(O(n\log n)\)。
注意极小值要开很大= =
//知识点:贪心
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 5e5 + 10;
const LL kInf = 1e18 + 2077;
//=============================================================
int n, k, a[kN];
LL ans = -kInf, suml, sumr, sum[kN], val[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(LL &fir, LL sec) {
if (sec > fir) fir = sec;
}
//=============================================================
int main() {
n = read(), k = read() + 1;
for (int i = 0; i < n; ++ i) a[i] = read();
std::sort(a, a + n);
for (int i = n - 1; i >= 0; -- i) {
sumr += 1ll * i * a[i];
sum[i] = sum[i + 1] + a[i];
}
for (int i = 0, j = 1, lth = 0; i < n; ++ i, ++ j) {
if (j == k + 1) {
j = 1;
++ lth;
}
Chkmax(ans, suml + 1ll * lth * sum[i] + sumr);
suml -= val[j];
val[j] += 1ll * lth * a[i];
suml += val[j];
sumr -= sum[i + 1];
}
printf("%lld\n", ans);
return 0;
}
总结
- 多测不清空,爆零两行泪。
- 简化题意要适度,不要丢失关键信息。
- 当发现自己的做法适用于更加一般的情况但复杂度较高时,大概率是自己想错了,否则出题人就会出更厉害的题目了= =
此时应重新读题尝试发现特殊性质。 - 发现自己的算法不可继续优化,或者思路略有瑕疵时,可以考虑推倒重来。
鸣谢:
Codeforces Round #687 (Div. 1) C. New Game Plus! () - けんちょんの競プロ精進記録
Editorial of Codeforces Round 687 (Technocup 2021 — Elimitation Round 2) - Codeforces