Codeforces Round 923 (Div. 3)

写在前面

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

一周前的题拖到现在哈哈好爽

A

找到最左和最右的 B 即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    int n; std::cin >> n;
    std::string s; std::cin >> s;
    int l = 0, r = 0;
    for (int i = 1; i <= n; ++ i) {
      if (s[i - 1] == 'B') {
        if (l == 0) l = i;
        r = i;
      }
    }
    if (l == r && l == 0) std::cout << "0\n";
    else std::cout << r - l + 1 << "\n";
  }
  return 0;
}

B

保证有解,直接按照题意构造即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    int n; std::cin >> n;
    int cnt[26] = {0};
    for (int i = 1; i <= n; ++ i) {
      int x; std::cin >> x;
      for (int j = 0; j < 26; ++ j) {
        if (cnt[j] == x) {
          std::cout << (char) (j + 'a');
          cnt[j] ++;
          break;
        }
      }
    }
    std::cout << "\n";
  }
  return 0;
}

C

有解当且仅当仅在数列 \(a, b\) 一方中出现的 \(1\sim k\) 中的权值种类数不大于 \(\frac{k}{2}\)

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const int kK = 1e6 + 10;
//=============================================================
int n, m, k, a[kN], b[kN];
bool yesa[kK], yesb[kK];
//=============================================================
bool Solve() {
  for (int i = 1; i <= k; ++ i) yesa[i] = yesb[i] = 0;
  for (int i = 1; i <= n; ++ i) yesa[a[i]] = 1;
  for (int i = 1; i <= m; ++ i) yesb[b[i]] = 1;

  int cnta = 0, cntb = 0;
  for (int i = 1; i <= k; ++ i) {
    if (!yesa[i] && !yesb[i]) return 0;
    if (yesa[i] && yesb[i]) continue;

    if (yesa[i]) ++ cnta;
    else ++ cntb;
  }
  return (2 * cnta <= k) && (2 * cntb <= k);
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m >> k;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    for (int i = 1; i <= m; ++ i) std::cin >> b[i];
    std::cout << (Solve() ? "YES\n" : "NO\n");
  }
  return 0;
}

D

傻逼题,但是我是傻逼。

首先对于每个位置预处理右侧第一个与其值不同的位置 \(\operatorname{next}\),简单倒序枚举即可。

对于一次区间查询 \(l, r\) 显然令 \(i=l, j = \operatorname{next}_l\) 最有可能有解,仅需检查 \(\operatorname{next}_l\) 是否不大于 \(r\) 即可。

然而赛时把题意抽象得太过了不仅预处理了一坨屎还写了个 ST 表简直是糖丸了

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const int kM = 1e6 + 10;
//=============================================================
int n, q, a[kN], next[kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    a[n + 1] = -1;
    for (int i = n; i; -- i) {
      if (a[i + 1] != a[i]) next[i] = i + 1;
      else next[i] = next[i + 1];
    }

    std::cin >> q;
    while (q --) {
      int l, r; std::cin >> l >> r;
      if (next[l] > r) std::cout << "-1 -1\n";
      else std::cout << l << " " << next[l] << "\n";
    }
  }
  return 0;
}

E

要求相邻的长度为 \(k\) 的区间和至多只有两种,则考虑向右进行滑动窗口的过程,说明滑动过程中区间和不断地交替加减 1,说明每次添加与删除的数的差值是交替的 \(\plusmn 1\),看起来是和奇偶性很有关的。

纯手玩有点麻烦于是打了个表发现了这样一种构造方法:考虑将 \(n\) 个位置每 \(k\) 个数作为一段,考虑按顺序每次填入所有段的同一位置 \(a_{i}, a_{k + i}, a_{2\times k + i}\cdots\):若 \(i\) 为奇数则从 1 开始递增地填,否则从 \(n\) 开始递减地填。

\(n=10, k=4\) 为例:(1 10 4 7) (2 9 5 6) (3 8

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, k, ans[kN];
//=============================================================
void Solve() {
  int now1 = 1, now2 = n;
  for (int i = 1; i <= k; ++ i) {
    for (int j = i; j <= n; j += k) {
      if (i % 2 == 1) ans[j] = now1 ++;
      else ans[j] = now2 --;
    }
  }
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> k;
    Solve();
    for (int i = 1; i <= n; ++ i) std::cout << ans[i] << " ";
    std::cout << "\n";
  }
  return 0;
}
/*
1 7 2 6 3 5 4

1 7 3 5 2 6 4

1 7 3       2

1 9 3 7 2 8 4 6 

1 9 
*/

F

感觉这太边双了于是抄了个板子来直接草过去了。

考虑对原图求所有边双连通分量,然后按权值递增枚举所有边直到某条边的两端点在同一边双中,然后以该边的一个端点为起点 DFS 找终点为另一端点的环即可。

忘了在什么地方被这个坑过了——求环并记录方案的经典算法:DFS 钦定每个点仅能经过一次,并且回溯时将回溯的结点从环中删除。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const int kM = 1e6 + 10;
//=============================================================
int n, m;
int edgenum = 1, head[kN], v[kM], ne[kM];
int dfnnum, dfn[kN], low[kN];
bool bridge[kM], vis[kN];
int dccnum;
int dccid[kN];
std::vector <int> dcc[kN];
struct Edge {
  int u, v, w;
} e[kM];

bool flag = 0;
std::vector <int> 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 Add(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
void Tarjan(int u_, int from_) {
  dfn[u_] = low[u_] = ++ dfnnum;
  for (int i = head[u_]; i > 1; i = ne[i]) {
    int v_ = v[i];
    if (!dfn[v_]) {
      Tarjan(v_, i);
      if (low[v_] > dfn[u_]) bridge[i] = bridge[i ^ 1] = 1;
      low[u_] = std::min(low[u_], low[v_]);
    } else if (i != (from_ ^ 1)) {
      low[u_] = std::min(low[u_], dfn[v_]);
    }
  }
}
void Dfs(int u_, int id_) {
  dccid[u_] = id_;
  dcc[id_].push_back(u_);
  for (int i = head[u_]; i > 1; i = ne[i]) {
    int v_ = v[i];
    if (dccid[v_] || bridge[i]) continue;
    Dfs(v_, id_);
  }
}
bool cmp(Edge fir_, Edge sec_) {
  return fir_.w < sec_.w;
}
void Init() {
  n = read(), m = read();

  edgenum = 1;
  dfnnum = 0;
  dccnum = 0;
  for (int i = 1; i <= n; ++ i) {
    head[i] = dfn[i] = low[i] = vis[i] = 0;
    dccid[i] = 0;
  }
  for (int i = 1; i <= 2 * m; ++ i) bridge[i] = 0;

  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    if (u_ == v_) continue;
    e[i] = (Edge) {u_, v_, w_};
    Add(u_, v_), Add(v_, u_);
  }
  for (int i = 1; i <= n; ++ i) {
    if (!dfn[i]) Tarjan(i, 0);
  }
  for (int i = 1; i <= n; ++ i) {
    if (!dccid[i]) Dfs(i, ++ dccnum);
  }
}
void Dfs2(int u_, int fa_, int end_) {
  vis[u_] = 1;
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (flag) return ;
    if (v_ == end_ && v_ != fa_) {
      flag = 1;
      return ;
    }
    if (vis[v_]) continue;
    ans.push_back(v_);
    Dfs2(v_, u_, end_);
    if (flag) return ;
    ans.pop_back();
  }
}
void Solve() {
  std::sort(e + 1, e + m + 1, cmp);
  for (int i = 1; i <= m; ++ i) {
    int u_ = e[i].u, v_ = e[i].v;
    if (dccid[u_] == dccid[v_]) {
      std::cout << e[i].w << " ";
      flag = 0;
      ans.clear();
      ans.push_back(u_), ans.push_back(v_);
      vis[v_] = vis[u_] = 1;
      Dfs2(v_, u_, u_);

      std::cout << ans.size() << "\n";
      for (auto x: ans) std::cout << x << " ";
      std::cout << "\n";
      return ;
    }
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    Init();
    Solve();
  }
  return 0;
}
/*
1
6 6
1 2 1
2 3 1
3 1 1
4 5 1
5 6 1
6 4 1
*/

G

好玩的 DP 状态。

如果只能向右边刷漆是套路二维 DP,记 \(f_{i, j}\) 表示使用前 \(i\) 个格子将 \(1\sim j\) 刷漆的最小代价,转移时考虑第 \(i\) 个格子是否使用即可,时空复杂度 \(O(n^2)\) 级别。

但这题可以向左右刷漆,于是可能出现某时刻被刷漆区域并不是一段连续的前缀的情况,上述状态无法描述,于是考虑再加一维来描述上述形态。发现上述状态的实质是:向右侧刷漆时仅需关注最右侧的已被涂色的格子。类似地可以发现,需要向左侧刷漆时仅需关注最左侧的未被涂色的格子的位置,于是修改状态,记 \(f_{i, j, k}\) 表示使用前 \(i\) 个格子刷漆,使得最左侧未被涂色的格子为 \(j\),最右侧的已被涂色的格子\(k\) 时的最小代价。

初始化 \(\forall i, j, k, f_{i, j, k} = \infin,\ f_{0, 1, 0} = 0\),转移时考虑第 \(i\) 个格子是否使用,若使用向左还是向右:

  • 若不使用,则有:

    \[f_{i - 1, j, k} \rightarrow f_{i, j, k} \]

  • 若向左,则可以覆盖 \([\max(1, i - a_i + 1), i]\)。若有 \(i - a_i + 1 \le j\),则可以全部涂色前缀 \(1\sim i\),则此时最右侧的已涂色格子变为 \(r = \max(i, k)\),则有转移:

\[f_{i - 1, j, k} + 1\rightarrow f_{i, r + 1, r} \]

  • 若向右,则可以覆盖 \([i, \min(i + a_i - 1, n)]\),则最右侧的已涂色格子变为 \(r = \max(k, \min(i + a_i - 1, n))\)。此时考虑是否会对最左侧的未被涂色格子产生影响,则有:

\[f_{i - 1, j, k} + 1\rightarrow \begin{cases} &f_{i, j, r} &(j < i)\\ &f_{i, r + 1, r} &\text{otherwise} \end{cases}\]

答案即为 \(f_{n, n + 1, n}\)

时空复杂度均为 \(O(n^3)\) 级别,题解里说这题可以 \(O(n^2)\),感觉很牛逼。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 110;
//=============================================================
int n, a[kN], f[kN][kN][kN];
//=============================================================
void Init() {
  std::cin >> n;
  for (int i = 1; i <= n; ++ i) std::cin >> a[i];
}
void DP() {
  for (int i = 0; i <= n; ++ i) {
    for (int j = 0; j <= n + 1; ++ j) {
      for (int k = 0; k <= n; ++ k) {
        f[i][j][k] = n;
      }
    }
  }

  f[0][1][0] = 0;
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n + 1;++ j) {      
      for (int k = 0; k <= n; ++ k) {
        f[i][j][k] = std::min(f[i][j][k], f[i - 1][j][k]);

        int l = std::max(i - a[i] + 1, 1);
        if (l <= j) {
          int nk = std::max(i, k);
          f[i][nk + 1][nk] = std::min(f[i][nk + 1][nk], f[i - 1][j][k] + 1);
        }

        int r = std::min(i + a[i] - 1, n);
        int nk = std::max(k, r);
        if (j < i) {
          f[i][j][nk] = std::min(f[i][j][nk], f[i - 1][j][k] + 1);
        } else {
          f[i][nk + 1][nk] = std::min(f[i][nk + 1][nk], f[i - 1][j][k] + 1);
        }
      }
    }
  }
  std::cout << f[n][n + 1][n] << "\n";
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    Init();
    DP();
  }
  return 0;
}

写在最后

参考:https://zhuanlan.zhihu.com/p/681725397

学到了什么:

  • E:打表!
  • F:找环经典算法。
  • G:有趣的状态设计。
posted @ 2024-02-14 05:27  Luckyblock  阅读(59)  评论(2编辑  收藏  举报