Codeforces Round 920 (Div. 3)

写在前面

比赛地址:https://codeforces.com/contest/1921

写完 C 题去泡了个面边吃边看 D,吃着吃着不对劲味儿怎么这么冲一看过期两个月了我草

开个小号玩玩但是 div3 都 AK 不了了呃呃简直是糖丸了

还剩最后一门近代史,周四才考,开摆!

感觉除了离散可能有点拉其他都还好,C++概率论大物怎么考那么傻逼啊妈的不如不复习

A

四个点排个序即得左下角和右上角的点,即可直接求面积。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#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;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    std::pair <int, int> p[5];
    for (int i = 1; i <= 4; ++ i) p[i] = std::make_pair(read(), read());
    std::sort(p + 1, p + 4 + 1);
    printf("%d\n", (p[4].first - p[1].first) * (p[4].second - p[1].second));
  }
  return 0;
}

B

分别求两个数列中该位置为 1 且另一数列中该位置为 0 的位置的数量,取最大值即为答案。

正确性显然,最优方案显然是将该数列中满足上述条件的位置与另一数列中满足上述条件的位置交换,然后再删或补使得 1 的数量相等,总操作数即为上述所求。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int n;
std::string a, b;
int cnt[2];
//=============================================================
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;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    n = read();
    std::cin >> a;
    std::cin >> b;
    cnt[0] = cnt[1] = 0;
    for (int i = 0; i < n; ++ i) {
      if (a[i] == b[i]) continue;
      cnt[b[i] == '1'] ++;
    }
    printf("%d\n", std::max(cnt[0], cnt[1]));
  }
  return 0;
}

C

模拟。

考虑每个时间间隔是一直开机或者关机,取电量消耗的最小值即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int n, a, b;
LL f;
//=============================================================
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;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    n = read(), f = read(), a = read(), b = read();
    int last = 0, flag = 1;
    for (int i = 1; i <= n; ++ i) {
      int m = read(), t = m - last;
      f -= std::min(1ll * a * t, 1ll * b);
      if (f <= 0) flag = 0;
      last = m;
    }
    printf("%s\n", flag ? "YES" : "NO");
  }
  return 0;
}

D

仅关心对应位置值的差,显然可以对两个数列任意重排。

先考虑 \(n=m\) 的情况,则等价于重排数列以最大化对应位置的残差和,即最大化残差平方的和。由经典题 [NOIP2013 提高组] 火柴排队,则令 A 升序排序 B 降序排序即可。

然后考虑 \(n \le m\) 的情况。基于上述排序后两数列的形态贪心地考虑,应使得 A 中的较小值对应 B 中的较大值,A 中的较大值对应 B 中的最小值,即 A 升序排序 B 降序排序后,A 的一段前缀对应 B 的等长的后缀,除去这段前缀的 A 的后缀对应 B 的等长的前缀。于是先预处理 A 所有长度的前/后缀与 B 等长的后/前缀对应时的贡献和,再枚举上述前缀和后缀的分界线统计答案取最大值即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, m, a[kN], b[kN];
LL sum1[kN], sum2[kN], ans;
//=============================================================
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;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  int T = read();
  while (T -- ) {
    n = read(), m = read();
    for (int i = 1; i <= n; ++ i) a[i] = read();
    for (int i = 1; i <= m; ++ i) b[i] = read();
    std::sort(a + 1, a + n + 1);
    std::sort(b + 1, b + m + 1);
    
    sum1[0] = sum2[n + 1] = ans = 0;
    for (int i = 1, j = m; i <= n; ++ i, -- j) sum1[i] = sum1[i - 1] + 1ll * abs(b[j] - a[i]);
    for (int i = n, j = 1; i >= 1; -- i, ++ j) sum2[i] = sum2[i + 1] + 1ll * abs(b[j] - a[i]);
    for (int i = 0; i <= n; ++ i) ans = std::max(ans, sum1[i] + sum2[i + 1]);
    printf("%lld\n", ans);
  }
  return 0;
}

E

我没脑子妈的

显然 \(x_a \ge x_b\) 时必平局。

再考虑若横向上两人每步只能在向对方靠近或距离不变,且两人距离不算太远(一直靠近可以相遇),则胜负仅与两人纵向距离差 \(x_b - x_a\) 的奇偶性有关。当 \(x_b - x_a\) 为奇时 Alice 必胜,否则 Bob 必胜。

再考虑上两人可选择横向上与对方远离,发现后手可以选择追杀先手保持两者距离的奇偶性不变,即并不会影响胜负。但是必败者可以选择一直向远离必胜者的方向开溜,使得必胜者无法追上来达到平局。于是仅需在上一步判断胜负的基础上判断必败者是否可以在到达边界之前不被追上即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#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;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    int h = read(), w = read(), xa = read(), ya = read();
    int xb = read(), yb = read();
    
    int d = xb - xa;
    if (d <= 0) {
      printf("Draw\n");
      continue;
    }
    if (d % 2 == 0) {
      if (ya == yb) printf("Bob\n");
      else if (ya < yb) {
        printf("%s\n", xb - 2 * (yb - 1) >= xa ? "Bob" : "Draw");
      } else {
        printf("%s\n", xb - 2 * (w - yb) >= xa ? "Bob" : "Draw");
      }
    } else {
      xa += 1;
      if (ya < yb) ++ ya;
      else if (ya > yb) -- ya;

      if (ya == yb) printf("Alice\n");
      else if (ya < yb) {
        printf("%s\n", xb - 2 * (w - ya) >= xa ? "Alice" : "Draw");
      } else {
        printf("%s\n", xb - 2 * (ya - 1) >= xa ? "Alice" : "Draw");
      }
    }
  }
  return 0;
}

F

对于一次询问 \(s, d, k\),显然有 \(d\times k \le n\),一眼根号分治。

\(k\le \sqrt{n}\) 时直接暴力;\(k\ge \sqrt{n}\) 时肯定有 \(d\le \sqrt{n}\),于是考虑预处理 \(\operatorname{sum}(i, j)\) 表示询问 \(s = i(1\le i\le n), d = j(1\le j\le \sqrt{n}), k = \infin\) 时的答案,\(\operatorname{suf}(i, j)\) 表示询问 \(s = i, d = j, k = \infin\) 访问到的所有位置的和。则有:

\[\begin{aligned} \operatorname{suf}(i, j) &= \operatorname{suf}(i + j, j) + a_j\\ \operatorname{sum}(i, j) &= \operatorname{sum}(i + j, j) + \operatorname{suf}(i + j, j) + a_i \end{aligned}\]

手玩下可以发现对于任意 \(d\le \sqrt{n}\) 的询问 \(s, d, k\) 答案即:

\[\operatorname{sum}(s,d) - \operatorname{sum}(s + d\times k,d) - k \times \operatorname{suf}(s + d\times k,d) \]

预处理时空复杂度均为 \(O(n\sqrt{n})\) 级别,总时间复杂度 \(O(n\sqrt{n} + m\sqrt{n})\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
const int kSqrtN = 320;
//=============================================================
int n, q, sqrtn, a[kN];
LL pre[kN + kSqrtN], suf[kN + kSqrtN][kSqrtN], sum[kN + kSqrtN][kSqrtN];
int s, d, k;
//=============================================================
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 Init() {
  n = read(), q = read();
  sqrtn = sqrt(n) + 1;
  for (int i = 1; i <= n; ++ i) a[i] = read();
  
  pre[0] = 0;
  for (int i = 1; i <= n; ++ i) pre[i] = pre[i - 1] + a[i];
  for (int i = 1; i <= sqrtn; ++ i) {
    for (int j = n + 1; j <= n + sqrtn + 1; ++ j) sum[j][i] = suf[j][i] = 0;    
    for (int j = n; j >= 1; -- j) {
      sum[j][i] = sum[j + i][i] + suf[j + i][i] + a[j];
      suf[j][i] = suf[j + i][i] + a[j];
    }
  }
}
LL Query() {
  LL ret = 0;
  if (k < sqrtn) {
    for (int i = 1, j = s; i <= k; ++ i, j += d) {
      ret += 1ll * i * a[j];
    }
    return ret;
  }
  int t = s + d * k;
  if (t > n) {
    return sum[s][d];
  } else {
    return sum[s][d] - sum[t][d] - 1ll * k * suf[t][d];
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    Init();
    while (q --) {
      s = read(), d = read(), k = read();
      printf("%lld ", Query());
    }
    printf("\n");
  }
  return 0;
}

G

\(n\times m\) 可过,看起来像什么暴力题。

以向右下射击为例,手玩下可以发现位于 \((x + 1, y)\) 比位于 \((x, y)\) 时覆盖的范围少了第 \(x\) 行的 \(k\) 个,多了对角线上的 \(k\) 个;位于 \((x, y + 1)\) 比位于 \((x, y)\) 时覆盖的范围少了第 \(y\) 列的 \(k\) 个,多了对角线上的 \(k\) 个。

发现可以通过维护行/列/对角线的前缀和来快速维护位置移动时覆盖范围内的贡献之和,于是可以先暴力枚举得到位于 \((1, 1)\) 时的贡献,再按顺序暴力枚举所有点同时维护贡献的改变即可。向其他三个方向射击同理。注意被影响到的范围可能会有部分越界,因为有 \(O(n\times m)\) 的时空限制所以注意特判。

总时空复杂度为 \(O(n\times m)\) 级别。

硬要说的话这东西也可以叫 DP 吧呃呃

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, m, k, ans;
//=============================================================
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 Solve() {
  n = read(), m = read(), k = read() + 1;
  std::vector <std::vector <int> > sum(n + 2, std::vector <int> (m + 2, 0));
  std::vector <std::vector <int> > sumz1(n + 2, std::vector <int> (m + 2, 0));
  std::vector <std::vector <int> > sumz2(n + 2, std::vector <int> (m + 2, 0));
  for (int i = 1; i <= n; ++ i) {
    std::string s;
    std::cin >> s;
    for (int j = 1; j <= m; ++ j) {
      sum[i][j] = (s[j - 1] == '#') + sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1];
      sumz1[i][j] = (s[j - 1] == '#') + sumz1[i - 1][j - 1];
      sumz2[i][j] += (s[j - 1] == '#') + sumz2[i - 1][j + 1];
    }
  }

  auto sumx = [=](int x_, int y_) {
    if (x_ <= 0 || x_ > n) return 0; 
    y_ = std::min(y_, m);
    y_ = std::max(y_, 0);
    return sum[x_][y_] - sum[x_ - 1][y_];
  };
  auto sumy = [=](int x_, int y_) {
    if (y_ <= 0 || y_ > m) return 0;
    x_ = std::min(x_, n);
    x_ = std::max(x_, 0);
    return sum[x_][y_] - sum[x_][y_ - 1];
  };
  auto sumz11 = [=](int x_, int y_) {
    if (x_ > n) {
      y_ -= x_ - n;
      x_ = n;
    }
    if (y_ > m) {
      x_ -= y_ - m;
      y_ = m;
    }
    if (x_ <= 0 || y_ <= 0) return 0;
    return sumz1[x_][y_];
  };
  auto sumz22 = [=](int x_, int y_) {
    if (x_ > n) {
      y_ += x_ - n;
      x_ = n;
    }
    if (y_ <= 0) {
      x_ += y_ - 1;
      y_ = 1;
    }
    if (x_ <= 0 || y_ > m) return 0;
    return sumz2[x_][y_];
  };

//RD 111
  int s = 0, s1 = 0;
  for (int i = 1; i <= k; ++ i) {
    s1 += sumx(i, k - i + 1);
  }
  for (int i = 1; i <= n; ++ i) {
    if (i != 1) {
      s1 = s1 - sumx(i - 1, k);
      s1 = s1 + sumz22(i + k - 1, 1) - sumz22(i - 1, k + 1);
    }
    s = s1;
    for (int j = 1; j <= m; ++ j) {
      ans = std::max(ans, s);
      s = s - sumy(i + k - 1, j) + sumy(i - 1, j);
      s = s + sumz22(i + k - 1, j + 1) - sumz22(i - 1, k + j + 1);
    }
  }
//RU 1111
  s = 0, s1 = 0;
  for (int i = 1; i <= k; ++ i) {
    s1 += sumx(n - i + 1, k - i + 1);
  }
  for (int i = n; i; -- i) {
    if (i != n) {
      s1 = s1 - sumx(i + 1, k); 
      s1 = s1 + sumz11(i, k);
    }
    s = s1;
    for (int j = 1; j <= m; ++ j) {
      ans = std::max(ans, s);
      s = s - sumy(i, j) + sumy(i - k, j);
      s = s + sumz11(i, k + j) - sumz11(i - k, j);
    }
  }
//LD 1111
  s = 0, s1 = 0;
  for (int i = 1; i <= k; ++ i) {
    s1 += sumx(i, m) - sumx(i, m - k + i - 1);
  }
  for (int i = 1; i <= n; ++ i) {
    if (i != 1) {
      s1 = s1 - sumx(i - 1, m) + sumx(i - 1, m - k); 
      s1 = s1 + sumz11(i + k - 1, m) - sumz11(i - 1, m - k);
    }
    s = s1;
    for (int j = m; j >= 1; -- j) {
      ans = std::max(ans, s);
      s = s - sumy(i + k - 1, j) + sumy(i - 1, j);
      s = s + sumz11(i + k - 1, j - 1) - sumz11(i - 1, j - k - 1);
    }
  }
//LU
  s = 0, s1 = 0;
  for (int i = 1; i <= k; ++ i) {
    s1 += sumx(n - i + 1, m) - sumx(n - i + 1, m - k + i - 1);
  }
  for (int i = n; i >= 1; -- i) {
    if (i != n) {
      s1 = s1 - sumx(i + 1, m) + sumx(i + 1, m - k); 
      s1 = s1 + sumz22(i, m - k + 1);
    }
    s = s1;
    for (int j = m; j >= 1; -- j) {
      ans = std::max(ans, s);
      s = s - sumy(i, j) + sumy(i - k, j);
      s = s + sumz22(i, j - k) - sumz22(i - k, j);
    }
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    ans = 0;
    Solve();
    printf("%d\n", ans);
  }
  return 0;
}

写在最后

学到了什么:

  • D:最小化两个数列对应位置残差和令第 \(k\) 大对应第 \(k\) 大;最大化则反之。
  • F:\(d\times k\le n\) 可根号分治。
  • G:关注两个相邻状态间被覆盖范围的更改。
posted @ 2024-01-16 16:00  Luckyblock  阅读(167)  评论(1编辑  收藏  举报