寫在前面
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