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)\),则有转移:
- 若向右,则可以覆盖 \([i, \min(i + a_i - 1, n)]\),则最右侧的已涂色格子变为 \(r = \max(k, \min(i + a_i - 1, n))\)。此时考虑是否会对最左侧的未被涂色格子产生影响,则有:
答案即为 \(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:有趣的状态设计。