Codeforces Round 926 (Div. 2)
写在前面
比赛地址:https://codeforces.com/contest/1929。
妈的状态差得一批打得像一拖,开学之前要好好调整了
A
签到。
\(\sum_{i=2}^{n} a_{i} - a_{i - 1} = a_n - a_1\),令 \(a_n = \max a_i, a_1 = \min a_i\) 即可。
//
/*
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 maxx = -1e9, minn = 1e9, n;
std::cin >> n;
for (int i = 1; i <= n; ++ i) {
int x; std::cin >> x;
if (x > maxx) maxx = x;
if (x < minn) minn = x;
}
std::cout << maxx - minn << "\n";
}
return 0;
}
B
知识点:手玩
一个显然的想法是尽量使选择的格子贡献为 2,手玩下发现可通过如下方案令 \(2\times n-2\) 个格子贡献为 2,2 个格子的贡献为 1:
- 先选择第一行所有格子,贡献均为 2。
- 选择最后一行除两边的所有格子,贡献均为 2。
- 选择最后一行两边的两个格子,贡献为 1。
发现不存在令 \(2\times n - 1\) 个格子贡献均为 2 的方案,则这样操作是最优的。
//手玩
/*
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, k; std::cin >> n >> k;
if (k <= 4 * n - 4) {
std::cout << (k + 1) / 2 << "\n";
} else {
std::cout << (2 * n - 2) + (k - 4 * n + 4) << "\n";
}
}
return 0;
}
C
知识点:贪心
和博弈完全无关的题。在这里衷心提醒各位搏一搏单车变陀螺,远离哔哩哔哩魔力赏,心动不如直接全款预购!
首先是题目的理解。注意到题干中提到 “Sasha can make bets so that for any outcome that does not contradict the rules described above, at some moment of time he will have at least \(n\) coins.”,可以猜到赌狗和赌场之间并不是对等的博弈关系。赌场可以任意选择某局赌狗是否输赢,来尽可能在使其亏钱的一次下注时终止连败状态使其永远不能回本。
代入赌狗的心理,则每局都应该想着一把回本并必须还能再赚一点。具体地,若之前连败了 \(i(0\le i\le k)\) 局,一共输了 \(s\) 元,则这把应当至少下注 \(\left\lfloor\frac{s}{k - 1}\right\rfloor + 1\) 元。若某次钱不够下注了说明赌狗被套进去了活该似了,否则才能够达成目的。
//贪心
/*
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 k, x, a; std::cin >> k >> x >> a;
LL s = 0;
for (int i = 0, j = 1; i <= x; ++ i) {
LL x = s / (k - 1) + 1;
s += x;
if (s > a) break;
}
std::cout << (1ll * a >= s ? "YES\n" : "NO\n");
}
return 0;
}
D
知识点:树形 DP
发现关键点集合法当且仅当不存在两关键点的 \(\operatorname{lca}\) 是另一关键点。这东西看着很树形 DP 的样子,考虑枚举子树的根,考虑以该点为 \(\operatorname{lca}\) 的关键点进行 DP。
脑子一抽设了个比较抽象的 DP 状态。记 \(f_{u, 0/1}\) 表示钦定节点 \(u\) 不选/选,在 \(u\) 的子树中以 \(u\) 为一端的链上有且仅有 1 个关键点且子树合法的以 \(u\) 为根的子树的关键点集数;类似地记 \(g_{u, 0/1}\) 表示钦定节点 \(u\) 不选/选,在 \(u\) 的子树中以 \(u\) 为一端的链上有且仅有 2 个关键点且子树合法的以 \(u\) 为根的子树的关键点集数。
注意上面的状态钦定子树中至少有 1 个关键点。显然有 \(\forall 1\le u\le n,\ f_{u, 1} = 1\) 恒成立。
然后考虑 DFS 转移。对于节点 \(u\),考虑枚举其所有子节点 \(v\):
-
对于 \(f_{u, 0}\),需要满足在 \(v\) 的子树中以 \(v\) 为一端的链上关键点数不大于 1 且关键点集不能全部为空。发现它们互不影响,则根据乘法原理:
\[f_{u, 0} = \prod_{v\in \operatorname{son}(u)} \left(f_{v,0} +f_{v,1} + 1\right) - 1 \] -
对于 \(g_{u, 0}\),需要满足有且仅有一个 \(v\) 满足在其子树中以 \(v\) 为一端的链上有且仅有 2 个关键点,则根据加法原理:
\[g_{u, 0} = \sum_{v\in \operatorname{son}(u)} \left(g_{v, 0} + g_{v, 1}\right) \] -
对于 \(g_{u, 1}\),需要满足有且仅有一个 \(v\) 满足在其子树中以 \(v\) 为一端的链上有且仅有 1 个关键点,则根据加法原理:
\[g_{u, 1} = \sum_{v\in \operatorname{son}(u)} (f_{v, 0} + f_{v, 1}) \]
答案即为(记得考虑上空集):
//树形 DP
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 3e5 + 10;
const LL p = 998244353;
//=============================================================
int n;
LL f[kN][2], g[kN][2];
std::vector <int> v[kN];
//=============================================================
void Dfs(int u_, int fa_) {
f[u_][1] = 1;
LL s = 1;
for (auto v_: v[u_]) {
if (v_ == fa_) continue;
Dfs(v_, u_);
s *= (f[v_][0] + f[v_][1] + 1), s %= p;
g[u_][0] += (g[v_][0] + g[v_][1]) % p, g[u_][0] %= p;
g[u_][1] += f[v_][0] + f[v_][1], g[u_][1] %= p;
}
f[u_][0] = (s - 1 + p) % p;
// std::cout << u_ << ":" << f[u_][0] << " " << g[u_][0] << " " << g[u_][1] << "\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 --) {
std::cin >> n;
for (int i = 1; i <= n; ++ i) {
f[i][0] = f[i][1] = g[i][0] = g[i][1] = 0;
v[i].clear();
}
for (int i = 1; i < n; ++ i) {
int u_, v_; std::cin >> u_ >> v_;
v[u_].push_back(v_);
v[v_].push_back(u_);
}
Dfs(1, 0);
std::cout << (f[1][0] + f[1][1] + g[1][0] + g[1][1] + 1) % p << "\n";
}
return 0;
}
E
知识点:状压 DP。
结论猜出来之后就是暴力 DP 了,但是结论不太显然,学到了。
发现关键路径数量 \(k\) 很小,并且数据范围提示 \(2^{k}<2^{20}\),容易想到状压 DP,用二进制记录已被覆盖了的关键路径集合,然后考虑枚举添加边进行转移。对于所有边,记 \(s_{i}\) 表示经过了边 \(i\) 的关键路径集合,这东西显然可以通过枚举关键路径 DFS \(O(k\times n)\) 地处理。
一个不太显然的结论是 \(t_i\) 的值至多仅有 \(2\times k\) 种,即树上 \(k\) 条路径的子集的路径交(不包括空路径)不超过 \(2\times k\) 种。证明自己 YY 不出来了,建议参考其他题解。于是考虑枚举这些值进行 DP。设 \(f_s\) 表示将关键路径集合 \(s\) 覆盖的最少边数,初始化 \(f_0 = 0\),\(\forall s\not= 0,\ f_{s} = \infin\) 转移时枚举 \(2\times k\) 种 \(t\) 相当于考虑添加一条边的影响,则有:
答案即为 \(f_{\Omega}\)。
时间复杂度 \(O(k\times n + k\times 2^k)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
const int kM = kN << 1;
const int kK = 21;
const int k2K = (1 << 20) + 10;
//=============================================================
int n, k;
int edgenum, head[kN], u[kM], v[kM], ne[kM];
std::vector <int> path, s1;
int flag, s[kM], f[k2K];
//=============================================================
void Add(int u_, int v_) {
v[++ edgenum] = v_;
u[edgenum] = u_;
ne[edgenum] = head[u_];
head[u_] = edgenum;
}
void Dfs(int u_, int fa_, int end_, int id_) {
if (u_ == end_) {
for (auto x: path) s[x] |= (1 << id_);
flag = 1;
return ;
}
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (flag) return ;
if (v_ == fa_) continue;
path.push_back(i), path.push_back(i ^ 1);
Dfs(v_, u_, end_, id_);
path.pop_back(), path.pop_back();
}
}
void Init() {
std::cin >> n;
edgenum = 1;
s1.clear();
for (int i = 1; i <= n; ++ i) head[i] = 0;
for (int i = 0; i < 2 * n; ++ i) s[i] = 0;
for (int i = 1; i < n; ++ i) {
int u_, v_; std::cin >> u_ >> v_;
Add(u_, v_), Add(v_, u_);
}
std::cin >> k;
for (int i = 1; i <= k; ++ i) {
int a, b; std::cin >> a >> b;
flag = 0;
Dfs(a, 0, b, i - 1);
}
std::sort(s, s + 2 * n);
s1.push_back(s[0]);
for (int i = 1; i < 2 * n; ++ i) {
if (s[i] != s[i - 1]) s1.push_back(s[i]);
}
// for (auto x: s1) std::cout << x << "---\n";
}
void DP() {
int all = (1 << k) - 1;
f[0] = 0;
for (int i = 1; i <= all; ++ i) f[i] = n;
for (int i = 0; i <= all; ++ i) {
for (auto j: s1) {
f[i | j] = std::min(f[i | j], f[i] + 1);
}
}
std::cout << f[all] << "\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;
}
F
gugug~
写在最后
学到了什么:
- C:博弈?贪心!
- E:树上 \(k\) 条路径的子集的路径交不超过 \(2\times k\) 种。