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

posted @ 2020-11-29 21:16  Luckyblock  阅读(311)  评论(6编辑  收藏  举报