AtCoder Beginner Contest 355 题解

AtCoder Beginner Contest 355 题解

A - Who Ate the Cake?

题意

有两个目击者,林戈和斯努克。林戈记得那个人 \(A\) 不是罪魁祸首,斯努克记得那个人 \(B\) 不是罪魁祸首。根据两名证人的记忆,确定是否可以唯一地识别罪犯。如果罪魁祸首可以确定,打印该人的号码。

思路

暴力枚举,看是否只有一个不确定是罪魁祸首。

代码

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 110;

int t[kMaxN], a, b, s;

int main() {
  cin >> a >> b;
  t[a]++, t[b]++;
  for (int i = 1; i <= 3; i++) {
    if (!t[i]) {
      if (s) {
        cout << "-1\n";
        return 0;
      }
      s = i;
    }
  }
  cout << s << '\n';
  return 0;
}

B - Piano 2

题意

我们得到了一个长度为 \(N\) 的序列 \(A\left(A_1,A_2,...,A_N\right)\) 和一个长度为 \(M\) 的序列 \(B\left(B_1,B_2,...,B_M\right)\)。在这里, \(A\)\(B\) 的所有元素都是成对不同的。确定按升序排列 \(A\)\(B\) 的所有元素形成的序列 \(C\left(C_1,C_2,...,C_N+M\right)\) 是否包含出现在 \(A\) 中的两个连续元素。

思路

用结构体存入,然后枚举 \(C\) 数组,看是否存在。

代码

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 1010;

struct node {
  int d, x;
} c[kMaxN];

int n, m;

bool cmp(node a, node b) {
  return a.x < b.x;
}

int main() {
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> c[i].x;
    c[i].d = 1;
  }
  for (int i = 1; i <= m; i++) {
    cin >> c[i + n].x;
  }
  sort(c + 1, c + 1 + n + m, cmp);
  for (int j = 1; j < n + m; j++) {
    if (c[j + 1].d == 1 && c[j].d == 1) {
      cout << "Yes\n";
      return 0;
    }
  }
  cout << "No\n";
  return 0;
}

C - Bingo 2

题意

宣布整数 A_i,并标记包含 A_i 的单元格。确定首次实现宾果游戏的开启时间。如果在 \(T\) 回合内未完成宾果游戏,则打印 \(-1\)。这里,实现宾果意味着满足以下条件中的至少一个:

  • 存在所有 \(N\) 单元格都被标记的行。
  • 存在标记了所有 \(N\) 单元格的列。
  • 存在一条对角线(从左上到右下或从右上到左下),其中标记了所有 \(N\) 单元格。

思路

用两个数组和两个变量存每行、每列、对角线满足还差几个,差 \(0\) 个就输出,要用快读快写优化。

代码

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 2010;

int a[kMaxN], b[kMaxN], c, d, n, t;

inline int read() {
  int x = 0, f = 1;
  char ch = getchar();
  while (ch < '0' || ch > '9') {
    if (ch == '-')
      f = -1;
    ch = getchar();
  }
  while (ch >= '0' && ch <= '9')
    x = x * 10 + ch - '0', ch = getchar();
  return x * f;
}

void write(int x) {
  if (x < 0)
    putchar('-'), x = -x;
  if (x > 9)
    write(x / 10);
  putchar(x % 10 + '0');
  return;
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  n = read(), t = read();
  for (int i = 1, x; i <= t; i++) {
    x = read();
    int h = (x + n - 1) / n, l = x % n;
    if (!l) {
      l = n;
    }
    a[h]++, b[l]++;
    if (h == l) {
      c++;
    }
    if (h + l == n + 1) {
      d++;
    }
    if (c == n || d == n) {
      write(i);
      return 0;
    }
    for (int j = 1; j <= n; j++) {
      if (a[j] == n || b[j] == n) {
        write(i);
        return 0;
      }
    }
  }
  write(-1);
  return 0;
}

D - Intersecting Intervals

题意

给定实数的 \(N\) 区间。第 \(i\)\(\left(1\le i\le N\right)\) 区间是 \([l_i,r_i]\) 。找出使第 \(i\) 和第 \(j\) 区间相交的对数 \(\left(i,j\right)\left(1\le i\le j\le N\right)\)

思路

把每个点的位置与种类(\(l\)\(r\)),每到一个起点就加上前面没有结束的线段数量。

代码

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 2010;

int a[kMaxN], b[kMaxN], c, d, n, t;

inline int read() {
  int x = 0, f = 1;
  char ch = getchar();
  while (ch < '0' || ch > '9') {
    if (ch == '-')
      f = -1;
    ch = getchar();
  }
  while (ch >= '0' && ch <= '9')
    x = x * 10 + ch - '0', ch = getchar();
  return x * f;
}

void write(int x) {
  if (x < 0)
    putchar('-'), x = -x;
  if (x > 9)
    write(x / 10);
  putchar(x % 10 + '0');
  return;
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  n = read(), t = read();
  for (int i = 1, x; i <= t; i++) {
    x = read();
    int h = (x + n - 1) / n, l = x % n;
    if (!l) {
      l = n;
    }
    a[h]++, b[l]++;
    if (h == l) {
      c++;
    }
    if (h + l == n + 1) {
      d++;
    }
    if (c == n || d == n) {
      write(i);
      return 0;
    }
    for (int j = 1; j <= n; j++) {
      if (a[j] == n || b[j] == n) {
        write(i);
        return 0;
      }
    }
  }
  write(-1);
  return 0;
}

E - Guess the Sum

题意

给定一个正整数 \(N\) 和整数 \(L\)\(R\),使 \(0\le L\le R\le2^N\)。您的目标是在 \(A_L+A_{L+1}+...+A_R\) 除以 \(100\) 时找到余数。

思路

\(\left(L−a,L\right)\left(L,L+a\right)\left(R-b,R\right)\left(R,R+b\right)\) 如果 \(a<b\) ,则后两个查询是不必要的,如果 \(a>b\) ,则前两个查询是不必要的。这允许我们将问题减少到半开间隔,其中 LSB 在 \(O\left(1\right)\) 时间内严格大于这个间隔。

代码

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 1e6 + 100;

int d[kMaxN], e[kMaxN], n, l, r, sum, t;
queue<int> q;
vector<array<int, 3>> ans;

void R(int x, int y) {
  if (0 <= x && x <= (1 << n) && d[x] == -1) {
    d[x] = y, q.push(x);
  }
}

int main() {
  cin >> n >> l >> r;
  for (int i = 0; i <= n; i++) {
    e[1 << i] = i;
  }
  fill(d, d + (1 << n) + 1, -1);
  for (R(l, 0); q.size(); q.pop()) {
    for (int i = 1; i <= (!q.front() ? (1 << n) : (q.front() & (-q.front()))); i *= 2) {
      R(q.front() - i, q.front()), R(q.front() + i, q.front());
    }
  }
  for (r++; r != l; ans.push_back({e[abs(r - d[r])], min(r, d[r]) / abs(r - d[r]), (d[r] < r ? 1 : -1)}), r = d[r]) {
  }
  for (auto i : ans) {
    cout << "? " << i[0] << ' ' << i[1] << '\n';
    cin >> t, sum = (sum + 100 + i[2] * t) % 100;
  }
  cout << "! " << sum << '\n';
  return 0;
}

F - MST Query

题意

给定一棵 \(n\) 个点的树,每条边为边权为 \(c_i\) 的边 \(\left(a_i,b_i\right)\) ,回答 \(q\) 次询问,每次询问加一条边权为 \(w_i\) 的边 \(\left(u_i,v_i\right)\),回答此时的最小生成树边权和。

思路

注意到值域只有 \(10\)。维护 \(10\) 个并查集,第 \(i\) 个并查集维护只有边权 \(\le i\) 的边对应的连通情况。显然对于更小的 \(i\),若 \(x,y\) 联通,则对于 \(j>i\) 也联通,但更小的更优。假设第 \(i\) 的并查集里的边数为 \(sz_i\),则需要用到权值为 \(i\) 的边为 \(sz_i-sz_{i-1}\) 条。

代码

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 2e5 + 10, kMaxM = 20;

int sz[kMaxN], fa[kMaxM][kMaxN], n, m;

int find_f(int k, int x) { return x == fa[k][x] ? x : fa[k][x] = find_f(k, fa[k][x]); }

int main() {
  cin >> n >> m;
  for (int i = 1; i <= 10; i++) {
    for (int j = 1; j <= n; j++) {
      fa[i][j] = j;
    }
  }
  for (int i = 1, u, v, w; i < n; i++) {
    cin >> u >> v >> w;
    for (int j = w; j <= 10; j++) {
      (find_f(j, u) ^ find_f(j, v)) && (fa[j][fa[j][u]] = fa[j][v], sz[j]++);
    }
  }
  for (int u, v, w, ans = 0; m--; ans = 0) {
    cin >> u >> v >> w;
    for (int i = w; i <= 10; i++) {
      (find_f(i, u) ^ find_f(i, v)) && (fa[i][fa[i][u]] = fa[i][v], sz[i]++);
    }
    for (int i = 1; i <= 10; i++) {
      ans += i * (sz[i] - sz[i - 1]);
    }
    cout << ans << '\n';
  }
  return 0;
}

G - Baseball

题意

思路

对于 \(Totally monotone\) 矩阵 \(A\) 中的任意两列,元素的大小关系都是单调的如果把 \(A\) 中的 \(j\) 列看作 \(i\) 中的函数,它们彼此只相交一次.
彼此只有一次交集的函数群的最小值可以通过应用 \(convex hull trick\) 来求出 \(Convex hull trick\) 本来是用来寻找一阶函数群最小值的算法,如果函数群彼此只相交一次,则可以用类似的算法处理求两个函数的交点时使用二分搜索

代码

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

using namespace std;

#define rep(i, s, t) for (int i = (int)(s); i < (int)(t); ++i)
#define revrep(i, t, s) for (int i = (int)(t) - 1; i >= (int)(s); --i)
#define all(x) begin(x), end(x)
template <typename T>
bool chmax(T& a, const T& b) {
  return a < b ? (a = b, 1) : 0;
}
template <typename T>
bool chmin(T& a, const T& b) {
  return a > b ? (a = b, 1) : 0;
}

template <typename F>
long long golden_section_search(F f, long long lb, long long ub) {
  long long s = 1, t = 2;
  while (t < ub - lb + 2) std::swap(s += t, t);
  long long l = lb - 1, m1 = l + t - s, m2 = l + s;
  auto v1 = f(m1), v2 = f(m2);
  while (m1 != m2) {
    std::swap(s, t -= s);
    if (ub < m2 || v1 <= v2) {
      m2 = m1;
      v2 = v1;
      m1 = l + t - s;
      v1 = f(m1);
    } else {
      l = m1;
      m1 = m2;
      v1 = v2;
      m2 = l + s;
      v2 = m2 <= ub ? f(m2) : v1;
    }
  }
  return m1;
}

template <typename T, typename F>
std::vector<T> monge_shortest_path(int N, const F& cost) {
  const T INF = std::numeric_limits<T>::max() / 2;
  std::vector<T> dist(N, INF);
  auto f = [&](int i, int j) { return dist[i] + cost(i, j); };
  std::deque<std::pair<int, int>> dq;
  auto intersection = [&](int i1, int i2) {
    if (f(i1, i2) >= f(i2, i2)) return -1;
    int lb = i2, ub = N;
    while (ub - lb > 1) {
      int m = (lb + ub) / 2;
      if (f(i1, m) <= f(i2, m)) {
        lb = m;
      } else {
        ub = m;
      }
    }
    return lb;
  };
  auto add = [&](int i) {
    int x = -1;
    while (dq.size() >= 2 &&
           dq.back().second >= (x = intersection(dq.back().first, i))) {
      dq.pop_back();
    }
    if (dq.size() == 1) x = intersection(dq.back().first, i);
    dq.push_back({i, x});
  };
  auto get = [&](int i) {
    while (dq.size() >= 2 && f(dq.front().first, i) > f(dq[1].first, i)) {
      dq.pop_front();
    }
    dq.front().second = -1;
    return f(dq.front().first, i);
  };
  dist[0] = 0;
  add(0);
  for (int i = 1; i < N; ++i) {
    dist[i] = get(i);
    add(i);
  }
  return dist;
}

int main() {
  int N, K;
  cin >> N >> K;
  vector<ll> P(N + 1);
  rep(i, 1, N + 1) cin >> P[i];
  vector<ll> P0(N + 2), P1(N + 2);
  rep(i, 0, N + 1) {
    P0[i + 1] = P0[i] + P[i];
    P1[i + 1] = P1[i] + P[i] * i;
  }
  auto cost = [&](int i, int j) {
    if (i == 0 && j == N + 1) return (ll)1e18;
    if (i == 0) {
      return j * P0[j] - P1[j];
    }
    if (j == N + 1) {
      return (P1[N + 1] - P1[i]) - i * (P0[N + 1] - P0[i]);
    }
    int m = (i + j) / 2 + 1;
    return (P1[m] - P1[i]) - i * (P0[m] - P0[i]) + j * (P0[j] - P0[m]) -
           (P1[j] - P1[m]);
  };
  auto calc = [&](ll lambda) {
    auto dist = monge_shortest_path<ll>(
        N + 2, [&](int i, int j) { return cost(i, j) + lambda; });
    return dist[N + 1] - lambda * (K + 1);
  };
  ll lambda = golden_section_search([&](ll lambda) { return -calc(lambda); }, 0, 3e10);
  cout << calc(lambda) << '\n';
}
posted @ 2024-05-25 21:53  小熊涛涛  阅读(300)  评论(0编辑  收藏  举报