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\),有兩種操作:

  1. 花費 \(x\) 的代價,使得序列中任意一個 0 變為 1。
  2. 花費 \(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\) 的個數,顯然有:

\[f_{j} = \begin{cases} [a_j = 0] &(j + k >n)\\ f_{j + k} + [a_j=0] &(j + k\le n) \end{cases}\]

則操作 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}}\),得到的價值之和為:

\[0\times a_{p_0} + 1\times a_{p_1} + 2\times a_{p_2}+\cdots + i\times a_{p_i} \]

則問題等價於將所有數排成一個序列,並划分為不多於 \(k + 1\) 個有序段,每段的價值為 \(\sum_{i=0} i\times a_{p_{i}}\),求最大價值和。


對於這些有序段,可以發現一些結論:

  1. 貪心地想,需要讓較大的數貢獻次數較多,則這些有序段一定是 不遞減 的。
  2. 所有 \(a_i\ge 0\) 一定都 在同一段 中。
    正確性顯然,若存在兩 \(a_i\ge 0\) 不在同一段中,則可將它們合並,使其中一個的貢獻次數增加。
  3. 所有 \(a_i \ge 0\) 所在的一段一定是最長的一段。
    正確性也顯然,若這些數所在的段不是最長的,則最長的段一定全是 \(a_i <0\),可以將最長段后面的適量元素移到 \(a_i \ge 0\) 的前面,使得 \(a_i\ge 0\) 貢獻次數增加,且被調整的元素貢獻次數不增。
  4. 最長段外的段的長度差最大為 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM