Codeforces Round 917 (Div. 2)

写在前面

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

昨天玩富婆妹玩到凌晨一点好爽🥰🥰🥰因为我很喜欢妃爱所以把妃爱留到最后推🥰🥰🥰

临走十分钟和 dztle 讨论了下 F,dztle 给了个超有用牛逼结论回来花了半个小时就切了,好爽、、、

A

发现答案要么是负数要么是 0。

  • 原数列中有 0,则答案为 0 且不必修改。
  • 否则若原数列中负数个数为奇数,则不修改即为答案。
  • 否则随便找个位置改成 0。
//
/*
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 n = read();
    int cnt[4] = {0};
    for (int i = 1; i <= n; ++ i) {
      int a = read();
      if (a > 0) cnt[1] ++;
      if (a == 0) cnt[2] ++;
      if (a < 0) cnt[3] ++;
    }
    if (cnt[2]) {
      printf("0\n");
    } else if (cnt[3] % 2 == 1) {
      printf("0\n");
    } else {
      printf("1\n1 0\n");
    }
  }
  return 0;
}

B

手玩了下发现,当且仅当恰好进行 \(k(0\le k\le n)\) 次操作才可得到长度为 \(n-k\) 的字符串,且一共有 \(k+1\) 种:

\[\begin{aligned} &s_1, s_{k+2}, s_{k + 3}, \cdots, s_{n}\\ &s_2, s_{k+2}, s_{k + 3}, \cdots, s_{n}\\ &\cdots\\ &s_{k + 1}, s_{k+2}, s_{k + 3}, \cdots, s_{n} \end{aligned}\]

发现长度为 \(n-k\) 的字符串的后缀 \(s[2:n-k]\) 均相同,则种类数即为 \(s_1\sim s_{k+1}\) 中字符种类数,于是直接循环枚举 \(k\) 统计答案即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n;
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;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    n = read();
    scanf("%s", s + 1);
    LL ans = 1;
    int sum = 1, cnt[26] = {0};
    ++ cnt[s[1] - 'a'];
    for (int i = 2; i <= n; ++ i) {
      if (!cnt[s[i] - 'a']) ++ sum;
      ++ cnt[s[i] - 'a'];
      ans += sum;
    }
    printf("%lld\n", ans);
  }
  return 0;
}

C

经过一次操作 2 后数列清零,之后显然交替进行操作 1 2 每次获得 1 的价值是最优的。于是考虑在进行第一次操作 2 前要进行几次操作 1。又发现长度为 \(n\) 的数列进行一次操作 2 至多得到 \(n\) 的贡献,则一开始至多进行 \(2\times n\) 次操作 1,否则换成交替进行操作 1 2 更优。

发现 \(O(n^2)\) 可过,于是直接枚举一开始进行操作 1 的次数暴力对数列修改并统计贡献即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, k, d, a[kN], b[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;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    n = read(), k = read(), d = read();
    for (int i = 1; i <= n; ++ i) a[i] = read();
    for (int i = 1; i <= k; ++ i) b[i] = read();

    LL ans = 0;
    int cnt = 0;
    for (int i = 1, j = 1, lim = std::min(2 * n, d); i <= lim; ++ i, ++ j) {
      if (j == k + 1) j = 1;
      cnt = 0;
      for (int l = 1; l <= n; ++ l) cnt += (a[l] == l);
      ans = std::max(ans, 1ll * (d - i) / 2 + cnt);
      for (int l = 1; l <= b[j]; ++ l) ++ a[l];
    }
    printf("%lld\n", ans);
  }
  return 0;
}

D

妈的从这里就开始上强度了,C 还是 1600 D 就 2300 了妈的,后面更是两道 2500。

感觉挺好玩的数据结构。

先对数组 \(q\) 求逆序对解决按照 \(p_i\) 划分的每段 \(a_{i\times k + 0}\sim a_{i\times k + (k - 1)}\) 内的逆序对数,则考虑段间贡献时段内元素顺序无影响,即看做 \(p = 0, 1,\dots, k - 1\)

然后对于每段考虑之前所有段对该段的贡献。对于当前段 \(a_{i\times k + 0}\sim a_{i\times k + (k - 1)}\) 之前的所有段 \(a_{j\times k + 0}\sim a_{j\times k + (k - 1)}\),手玩下发现下规律:

  • \(\frac{a_{i}}{2^l}\le a_j<\frac{a_i}{2^{l-1}}(1\le l\le \min\{k-1, \left\lceil\log_2 a_i\right\rceil\})\),则有:
    • \(2^0\times a_i\le 2^{l+0}\times a_{j}<2^1\times a_i\),即:\(2^{l + 0}\times a_j\) 可与 \(a_i\) 组成逆序对,贡献为 1。
    • \(2^0\times a_i\le 2^{l + 1}\times a_{j}<2^2\times a_i\),即:\(2^{l + 1}\times a_j\) 可与 \(a_i,2\times a_i\) 组成逆序对,贡献为 2。
    • ……
    • \(2^0\times a_i\le 2^{l + (k-1-l)}\times a_{j}<2^{k-l}\times a_i\),即 \(2^{k-1}\times a_j\) 可与 \(2^0\times a_i \sim 2^{k-l-1}\times a_i\) 组成逆序对,贡献为 \(k-l\)
    • 发现贡献之和是公差为 1 的等差数列 \(1\sim k-l\) 之和,于是枚举 \(l\) 求权值在 \(\left[\frac{a_{i}}{2^l}, \frac{a_i}{2^{l-1}}\right)\) 中的数的数量套用公式并计算贡献即可。
  • \(2^{l-1}\times a_{i}\le a_j<2^l\times a_i(1\le l\le k)\),则有:
    • \(2^0\times a_i\le 2^{0}\times a_{j}<2^{l}\times a_i\),即:\(2^{0}\times a_j\) 可与 \(2^0\times a_i \sim 2^{l-1}\times a_i\) 组成逆序对,贡献为 \(l\)
    • \(2^0\times a_i\le 2^{1}\times a_{j}<2^{l+1}\times a_i\),即:\(2^{1}\times a_j\) 可与 \(2^0\times a_i \sim 2^{l}\times a_i\) 组成逆序对,贡献为 \(l+1\)
    • ……
    • \(2^0\times a_i\le 2^{k-l-1}\times a_{j}<2^{k-1}\times a_i\),即:\(2^{k-l-1}\times a_j\) 可与 \(2^0\times a_i \sim 2^{k-2}\times a_i\) 组成逆序对,贡献为 \(k-1\)
    • \(2^0\times a_i\le 2^{k-l}\times a_{j}<2^{k}\times a_i\),即:\(2^{k-l}\times a_j\) 可与 \(2^0\times a_i \sim 2^{k-1}\times a_i\) 组成逆序对,贡献为 \(k\)
    • \(\cdots\)
    • \(2^k\times a_i\le 2^{k}\times a_{j}\),即:\(2^{k}\times a_j\) 可与 \(2^0\times a_i \sim 2^{k-1}\times a_i\) 组成逆序对,贡献为 \(k\)
    • 发现贡献是公差为 1 的等差数列 \(l\sim k\) 之和加 \(k\times (k-l)\),同样枚举 \(l\) 求对应权值区间的数的数量套用公式并计算贡献即可。
  • \(2^{l-1}\times a_{i}\le a_j<2^l\times a_i(l\ge k+1)\):贡献即 \(k\times k\),做法同上。

求逆序对和维护区间数的数量都可以用权值树状数组解决,因为两个数列均为排列,再考虑上求权值区间时枚举 \(a_i\) 的倍数,总时间复杂度 \(O(k\log k + n\log^2 n)\) 级别。

//知识点:序列数据结构,权值树状数组,枚举
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const LL p = 998244353;
//=============================================================
int n, m, a[kN], b[kN];
LL ans, sum;
//=============================================================
namespace Bit {
  #define lowbit(x) ((x)&(-x))
  int lim, t[kN << 1];
  void Init(int n_) {
    lim = n_;
    for (int i = 1; i <= lim; ++ i) t[i] = 0;
  }
  void Insert(int pos_, int val_) {
    for (int i = pos_; i <= lim; i += lowbit(i)) {
      t[i] += val_;
    }
  }
  int Sum(int pos_) {
    int ret = 0;
    for (int i = pos_; i; i -= lowbit(i)) {
      ret += t[i];
    }
    return ret;
  }
  int Query(int l_, int r_) {
    if (l_ > r_) return 0;
    return Sum(r_) - Sum(l_ - 1);
  }
  #undef lowbit
}
void Init() {
  std::cin >> n >> m;

  ans = sum = 0;
  for (int i = 1; i <= n; ++ i) std::cin >> a[i];
  for (int i = 1; i <= m; ++ i) std::cin >> b[i];
}
void Solve1() {
  Bit::Init(m);
  for (int i = 1; i <= m; ++ i) {
    ++ b[i];
    sum += Bit::Query(b[i] + 1, m);
    Bit::Insert(b[i], 1);
  }
  ans = (ans + (sum * n) % p) % p;
}
void Solve2() {
  Bit::Init(2 * n - 1);
  for (int i = 1; i <= n; ++ i) {
    for (int l, r = a[i], j = 0, k = 2; ; ++ j, k <<= 1) {
      l = r + 1, r = std::min(k * a[i] - 1, 2 * n - 1);
      if (l > 2 * n - 1) break;
      int c = Bit::Query(l, r);
      if (!c) continue;
      if (j > m) {
        ans = (ans + 1ll * c * m % p * m % p) % p;   
      } else {
        ans = (ans + 1ll * ((1 + j) + m) * (m - j) / 2ll % p * c % p) % p;
        ans = (ans + 1ll * c * j % p * m % p) % p; 
      }
    }

    for (int l = a[i], r, j = 1, k = 2; ; ++ j, k <<= 1) {
      r = l - 1, l = (int) ceil(1.0 * a[i] / k);
      if (l <= 0 || r <= 0 || l > r || j >= m) break;
      int c = Bit::Query(l, r);
      if (!c) continue;
      ans = (ans + 1ll * (1ll * (1 + (m - j)) * (m - j) / 2ll % p * c % p) % p) % p;
    }
    Bit::Insert(a[i], 1);
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(false);
  int T; std::cin >> T;
  while (T --) {
    Init();
    Solve1();
    Solve2();
    std::cout << ans << "\n";
  }
  return 0;
}
/*
1
1 5
1
0 1 2 3 4

1
8 3
5 1 7 11 15 3 9 13
2 0 1
*/

/*
1
3 3
1 3 5
2 1 0
*/
/*
1
8 3
5 1 7 11 15 3 9 13
2 0 1
*/
/*
1
3 1
3 5 1
0
*/

E

构造,妈的我没脑子。第一眼还看着像网络流呃呃。

  • 首先 \(k\) 是奇数无解。
  • \(4 | k\) 时:发现将一个 \(2\times 2\) 的方阵全部改成 1/0 后仍合法,发现按顺序将每个 \(2\times 2\) 的方阵改为 1 即可。
  • \(k\bmod 4 = 2\) 时:
    • \(n=2\) 时,\(k=2\) 有解;\(n>2\)\(k=2\)\(k=n^2-2\) 无解。
    • 先考虑 \(k=6\),发现可以构造成下列形式:
      1 1 0 0 ...
      1 0 1 0 ...
      0 1 1 0 ...
      0 0 0 0 ...
      
      于是钦定左上角是上述的 \(4\times 4\) 的方阵,然后考虑剩下的 \(k-6\) 个 1 如何放置。
    • 此时有 \(4| k - 6\)。发现除去上面的 \(4\times 4\) 的方阵可以构成 \(\frac{n^2-16}{4}\)\(2\times 2\) 的方阵,则 \(6 \le k<n^2 - 6\) 都解决了。
    • \(k=n^2-6\) 时仅需将左上角的 \(4\times 4\) 方阵改为:
      1 1 1 1 ...
      1 0 1 0 ...
      0 1 1 0 ...
      0 0 1 1 ...
      
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2010;
//=============================================================
int n, k;
bool ans[kN][kN];
//=============================================================
void Solve1() {
  k /= 4;
  for (int i = 1; i <= n / 2; ++ i) {
    for (int j = 1; j <= n / 2; ++ j) {
      for (int x = 2 * i - 1; x <= 2 * i; ++ x) {
        for (int y = 2 * j - 1; y <= 2 * j; ++ y) {
          ans[x][y] = (k > 0);
        }
      }
      -- k;
    }
  }
}
void Solve2() {
  for (int x = 1; x <= 4; ++ x) {
    for (int y = 1; y <= 4; ++ y) {
      ans[x][y] = 0;
    }
  }
  ans[1][1] = ans[1][2] = ans[2][1] = ans[2][3] = ans[3][2] = ans[3][3] = 1;
  if (k == n * n - 6) ans[1][3] = ans[1][4] = ans[4][3] = ans[4][4] = 1, k -= 4;
  k -= 6;

  k /= 4;
  for (int i = 1; i <= n / 2; ++ i) {
    for (int j = 1; j <= n / 2; ++ j) {
      if (i <= 2 && j <= 2) continue;
      for (int x = 2 * i - 1; x <= 2 * i; ++ x) {
        for (int y = 2 * j - 1; y <= 2 * j; ++ y) {
          ans[x][y] = (k > 0);
        }
      }
      -- k;
    }
  }
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(false);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> k;
    if (k % 2 == 1) {
      std::cout << "No\n";
      continue;
    }
    if (n == 2) {
      if (k == 0) std::cout << "Yes\n0 0\n0 0\n";
      if (k == 2) std::cout << "Yes\n1 0\n0 1\n";
      if (k == 4) std::cout << "Yes\n1 1\n1 1\n";
      continue;
    }
    if (k == n * n - 2 || k == 2) {
      std::cout << "No\n";
      continue;
    }
    std::cout << "Yes\n";
    if (k % 4 == 0) Solve1();
    else Solve2();

    for (int x = 1; x <= n; ++ x) {
      for (int y = 1; y <= n; ++ y) {
        std::cout << ans[x][y] << " ";
      }
      std::cout << "\n";
    }
  }
  return 0;
}
/*
1 1 1 1 1 1
1 1 1 1 1 1
1 1 0 0 0 0
1 1 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0

1 1 0 0 0 0
1 0 1 0 0 0
0 1 1 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0

1 1 1 1 0 0
1 0 1 0 0 0
0 1 1 0 0 0
0 0 1 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0
*/

F

最智力巅峰的一集。

先和 dztle 讨论出一堆结论:

首先若存在 \(l_{i} + l_{j}>d\) 显然无解。

否则若仅存在唯一的 \(l_i>\frac{d}{2}\),则这条边一定在直径上,则此时最可行的构造是用其他边构造出一条长为 \(d-l_i\) 的链与 \(l_i\) 相连构成直径,然后将其他边直接接到 \(l_i\) 靠近直径中点的端点。直接背包判断即可。

否则考虑先构造出长为 \(d\) 的直径,考虑其他边应该接到哪里:

  • 发现所有边直接连到距离直径的中点最近的点 \(m\)上是最可行的。
  • \(m\) 与较近的直径端点距离为 \(d'\)(显然有 \(d'\le \frac{d}{2}\))。则若所有边的长度不大于 \(d'\) 说明构造合法。
  • 为什么合法?考虑一条边 \(l_i(l_i\le d')\) 位于该构造中的什么位置:
    • 边在 \(m\) 与较近的直径端点的链上,则显然有 \(l_i\le d'\le \frac{d}{2}\)
    • 边在 \(m\) 与较远的直径端点的链上,则有 \(l_i<d'<\frac{d}{2}, d'+l_i\le d\),则直径合法。
    • 边不在直径上且与 \(m\) 相连,则有 \(d'+l_i\le d, d - d' + l_i\le d - d' + d' = d\),则直径合法。
  • 由上述讨论可知当 \(\forall 1\le i\le n, l_i\le d'\) 时在这棵树上所有边都有自己要做的事,所以构造合法。否则必然出现与直径为 \(d\) 冲突的情况。

于是同样考虑背包,首先想到设 \(f_i\) 表示构造出长度为 \(i\) 的链时,最靠近 \(\frac{d}{2}\) 的位置与较近的端点的距离。初始化 \(f_0=0, \forall 1\le i\le d, f_i = -\infin\),转移时枚举当前要加入的边 \(l_i\),再枚举加入边后的链长 \(j\),则有转移:

\[\begin{cases} f_j\leftarrow f_{j-l_i} + l_i &(f_{j - l_i} + l_i\le \frac{d}{2})\\ f_j\leftarrow f_{j - l_i} &\text{otherwise} \end{cases}\]

然后发现这个状态有点问题,有可能长度为 \(l-a_i\) 的链存在多种构造使得最靠近 \(\frac{d}{2}\) 的位置与较近的端点的距离不同,为了考虑周全应当把这些情况都记录下来。于是考虑修改状态将 \(f\) 设为 bitset,表示构造出长度为 \(i\) 的链时,最靠近 \(\frac{d}{2}\) 的位置与较近的端点的距离为某个值是否可行。初始化 \(f_{0, 0} = \text{true}\) 同样修改上述两种转移的形式:

  • \(f_j\leftarrow f_{j-l_i} + l_i\),等价于 \(f_j\) 按位或上 \(f_{j-l_i}\) 左移 \(l_i\) 位的前 \(\frac{d}{2}\) 位。
  • \(f_j\leftarrow f_{j - l_i}\),等价于 \(f_j\) 直接或上 \(f_{j-l_i}\)

最后检查 \(f_d\) 最右侧的 1 的位置是否大于最长的边即可。

总时间复杂度大概是 \(O\left( \frac{n^2d}{w} \right)\)\(w = 64\),反正能过吸吸

//知识点:背包
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2010;
//=============================================================
int n, d, ans, a[kN];
bool g[kN];
std::bitset <kN> f[kN], h;
//=============================================================
bool DP1() {
  g[0] = 1;
  for (int i = 1; i <= d; ++ i) g[i] = 0;
  for (int i = 1; i < n; ++ i) {
    for (int j = d - a[n]; j >= a[i]; -- j) {
      g[j] |= g[j - a[i]];
    }
  }
  return g[d - a[n]];
}
bool DP2() {
  h.reset();
  for (int i = 0; i <= d / 2; ++ i) h.set(i);
  for (int i = 0; i <= d; ++ i) f[i].reset();
  f[0].set(0);
  
  for (int i = 1; i <= n; ++ i) {
    for (int j = d; j >= a[i]; -- j) {
      f[j] |= f[j - a[i]] | ((f[j - a[i]] << a[i]) & h);
    }
  }
  return f[d]._Find_next(a[n] - 1) != f[d].size();
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(false);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> d;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    std::sort(a + 1, a + n + 1);
    if (a[n] + a[n - 1] > d) {
      ans = 0;
    } else if (a[n] > d / 2) {
      ans = DP1();
    } else {
      ans = DP2();
    }
    std::cout << (ans ? "Yes\n" : "No\n");
  }
  return 0;
}
/*
5
2 1
1 1

2 5
3 2

3 5
3 2 1

3 6
3 2 1

3 6
3 3 4
*/
/*
1
3 6
3 2 1
*/

写在最后

学到了什么:

  • D:别惦记你那 b 权值线段树了,单点修改还不写树状数组?
  • E:可以将部分区域拿出来单独构造,使得其余部分满足其他构造从而直接套用。
  • F:典中典之距离某点最远的点一定在直径上。

现在继续玩富婆妹🥰🥰🥰

posted @ 2024-01-26 00:10  Luckyblock  阅读(28)  评论(0编辑  收藏  举报