写在前面

更好的阅读体验:https://www.cnblogs.com/luckyblock/p/18785878

比赛地址:https://ac.nowcoder.com/acm/contest/103957

呃呃太唐了。

A

签到。

#include <bits/stdc++.h>
#define LL long long
int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int a, b, w; std::cin >> a >> b >> w;

  if (a == w || b == w || (a + b == w) || (a + w == b) || (b + w == a)) {
    std::cout << "Yes" << "\n";
  } else {
    std::cout << "No" << "\n";
  }
  return 0;
}

B

签到,枚举。

#include <bits/stdc++.h>
#define LL long long
int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int n; std::cin >> n;
  std::vector<int> a(n + 2);
  for (int i = 1; i <= n; ++ i) std::cin >> a[i];

  int ans = -1;
  for (int i = 2; i <= n - 1; ++ i) {
    if (a[i] > a[i - 1] && a[i] > a[i + 1]) {
      ans = std::max(ans, a[i] - (a[i - 1] + a[i + 1]) / 2);
    }
  }
  std::cout << ans << "\n";
  return 0;
}

C

特判。

保证最终剩下的所有桶中都不触发消除,则最终剩下的球数量的上界显然为

时若有解,则一种一定可行的操作方案是:不断往第一个桶里放球,直至恰好消除 个球,然后将剩余的球平均放到所有桶里,此时一定不会触发消除。则有解当且仅当:

#include <bits/stdc++.h>
#define LL long long

int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T; std::cin >> T;
  while (T --) {
    LL n, m, k, q;
    std::cin >> n >> m >> k >> q;
    
    int ans = (q <= m * (k - 1) && (n - q) % k == 0);
    
    std::cout << (ans ? "Yes" : "No") << "\n";
  }
  return 0;
}

D

树,枚举。

记节点 的度数为 。容易发现对于一棵以 为根的有根树,根节点 的孩子数为 ,其余所有点 的孩子数为

于是考虑预处理 的前后缀最大值 ,然后考虑枚举根节点 ,此时树的叉数 即为:

于是在所有的答案中按题意取最小值即可。

#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;

int n, into[kN], pre[kN], suf[kN];
std::vector<int> edge[kN];

int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  std::cin >> n;
  for (int i = 1; i < n; ++ i) {
    int u, v; std::cin >> u >> v;
    edge[u].push_back(v), edge[v].push_back(u);
    ++ into[u], ++ into[v];
  }
  for (int i = 1; i <= n; ++ i) pre[i] = std::max(pre[i - 1], into[i]);
  for (int i = n; i; -- i) suf[i] = std::max(suf[i + 1], into[i]);

  int ans1 = n, ans2 = 0;
  for (int i = 1; i <= n; ++ i) {
    int v = std::max(pre[i - 1] - 1, std::max(into[i], suf[i + 1] - 1));
    if (v < ans1) ans1 = v, ans2 = i;
  }
  std::cout << ans1 << " " << ans2 << "\n";
  return 0;
}

E

枚举,DP。

众所周知有:

发现数据范围很小,于是考虑每次询问时大力枚举被询问数 的所有因数 ,则题目所求即为能否使用给定数列凑出两个数

先考虑一个暴力 DP,记 使用数列前 个数,能否凑出行的和为 ,列的和为 。初始化 ,转移时大力枚举每个数 放到行/列,或者舍弃:

上述状态可以在 时间复杂度内预处理出来。则对于每次询问都大力枚举被询问书的因数 ,有解当且仅当存在 ,使得

这个时候写完代码准备交了,一看下面我草居然还要输出方案!?怎么搞?!

于是考虑改造一下 DP 状态,使之能够记录当前这步转移进行了什么操作即可。记 表示若 ,当前这步转移对 进行的操作为:舍弃/加到了行上/加到了列上。若 。在进行上述枚举转移 时顺便维护 即可。

每次询问时,若枚举到了一个合法的 满足 ,则考虑倒序枚举 ,并根据记录的 还原每一步的操作即可,总时间复杂度

实现时可以合并两个数组 ,具体实现详见代码。

注意:以下代码为根据上述暴力 DP 直接实现的,并不能通过 hard version 的 F 题。

#include <bits/stdc++.h>
#define LL long long

const int kN = 110;
const int kM = 110;

int n, m, a[kN];
int from[kN][kM][kM];

int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  std::cin >> n >> m;
  for (int i = 1; i <= n; ++ i) std::cin >> a[i];

  from[0][0][0] = -1;
  for (int i = 1; i <= n; ++ i) {
    for (int j = 0; j <= 100; ++ j) {
      for (int k = 0; k <= 100; ++ k) {
        if (from[i - 1][j][k]) from[i][j][k] = -1;
      }
    }

    for (int j = a[i]; j <= 100; ++ j) {
      for (int k = 0; k <= 100; ++ k) {
        if (!from[i][j][k] && from[i - 1][j - a[i]][k]) from[i][j][k] = 1;
      }
    }

    for (int j = 0; j <= 100; ++ j) {
      for (int k = a[i]; k <= 100; ++ k) {
        if (!from[i][j][k] && from[i - 1][j][k - a[i]]) from[i][j][k] = 2;
      }
    }
  }

  while (m --) {
    int x, ans = 0; std::cin >> x;

    for (int d = 1; d * d <= x; ++ d) {
      if (x % d) continue;
      if (from[n][d][x / d]) {
        ans = d;
        break;
      } else if (from[n][x / d][d]) {
        ans = x / d;
        break;
      }
    }

    std::cout << (ans ? "Yes" : "No") << "\n";
    if (!ans) continue;
    
    int suma = ans, sumb = x / ans;
    std::vector<int> ans1, ans2;
    for (int i = n; i; -- i) {
      if (from[i][suma][sumb] == -1) continue;
      if (from[i][suma][sumb] == 1) ans1.push_back(a[i]), suma -= a[i];
      if (from[i][suma][sumb] == 2) ans2.push_back(a[i]), sumb -= a[i];
    }
    std::cout << ans1.size() << " " << ans2.size() << "\n";
    for (auto v: ans1) std::cout << v << " ";
    std::cout << "\n";
    for (auto v: ans2) std::cout << v << " ";
    std::cout << "\n";
  }

  return 0;
}

F

DP,减去无用状态、无用转移的 DP 优化。

请首先阅读上一题 easy version 的题解。

发现此时直接套用上述状态直接做空间时间都会爆掉。但是发现数据范围并没有增大很多,于是考虑能否优化。

发现上述状态中,行和列实际上是没有区别的,状态 和状态 本质上是相同的,则实际上存在大量的无用状态和无用转移。

由于询问满足 ,根据小学数学知识可以发现,若 有解,即存在 ,则一定有:。于是考虑钦定在上述状态 中, 不大于 ,则对应的 一定不大于 (即钦定构造方案时保证行的和 不大于列的和 )。

此时状态 空间复杂度变为 级别,空间复杂度上没问题了。转移时钦定枚举 时不大于 不大于 ,则由调和级数可知,对于每个 转移次数为:

然后就能在 的时间复杂度内处理出所有状态了。询问时保证枚举的因数 ,然后套用 E 的做法即可。总时间复杂度

#include <bits/stdc++.h>
#define LL long long
 
const int kN = 510;
const int kM = 110;
 
int n, m, a[kN];
int from[kN][kM][kM * kM];
 
int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  std::cin >> n >> m;
  for (int i = 1; i <= n; ++ i) std::cin >> a[i];
 
  from[0][0][0] = -1;
  for (int i = 1; i <= n; ++ i) {
    for (int j = 0; j <= 100; ++ j) {
      for (int k = 0; k <= 100 * 100 / std::max(1, j); ++ k) {
        if (from[i - 1][j][k]) from[i][j][k] = -1;
      }
    }
 
    for (int j = a[i]; j <= 100; ++ j) {
      for (int k = 0; k <= 100 * 100 / std::max(1, j); ++ k) {
        if (!from[i][j][k] && from[i - 1][j - a[i]][k]) from[i][j][k] = 1;
      }
    }
 
    for (int j = 0; j <= 100; ++ j) {
      for (int k = a[i]; k <= 100 * 100 / std::max(1, j); ++ k) {
        if (!from[i][j][k] && from[i - 1][j][k - a[i]]) from[i][j][k] = 2;
      }
    }
  }
 
  while (m --) {
    int x, ans = 0; std::cin >> x;
    for (int d = 1; d * d <= x; ++ d) {
      if (x % d) continue;
      if (from[n][d][x / d]) {
        ans = d;
        break;
      }
    }
 
    std::cout << (ans ? "Yes" : "No") << "\n";
    if (!ans) continue;
    
    int suma = ans, sumb = x / ans;
    std::vector<int> ans1, ans2;
    for (int i = n; i; -- i) {
      if (from[i][suma][sumb] == -1) continue;
      if (from[i][suma][sumb] == 1) ans1.push_back(a[i]), suma -= a[i];
      if (from[i][suma][sumb] == 2) ans2.push_back(a[i]), sumb -= a[i];
    }
    std::cout << ans1.size() << " " << ans2.size() << "\n";
    for (auto v: ans1) std::cout << v << " ";
    std::cout << "\n";
    for (auto v: ans2) std::cout << v << " ";
    std::cout << "\n";
  }
 
  return 0;
}

写在最后

学到了什么:

  • F:减去无用状态、无用转移的 DP 优化。