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}) \]

答案即为(记得考虑上空集):

\[f_{1, 0} + f_{1, 1} + g_{1, 0} + g_{1, 1} + 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\) 相当于考虑添加一条边的影响,则有:

\[\forall t\in \{t_i\},\ f_{s} +1\rightarrow f_{s\cup 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\) 种。
posted @ 2024-02-19 02:28  Luckyblock  阅读(64)  评论(0编辑  收藏  举报