GDCPC2023 B , D , F , K 题解

和队友一起打的 2023 年广东省大学生程序设计竞赛重现赛,写了 B, D, K,胡了一个 F。

D

题目大意

随着广东的建设与发展,越来越多人选择来到广东开始新生活。在一片新建的小区,有 \(n\) 个人要搬进 \(m\) 栋排成一行的房子,房子的编号从 \(1\)\(m\)(含两端)。房子 \(u\)\(v\) 相邻,当且仅当 \(|u-v|=1\)。我们需要为每一个人安排一栋房子,要求所有人入住的房子互不相同。若两个人住进了一对相邻的房子,则这两个人互为邻居。

有的人喜欢自己有邻居,而有的人不喜欢。对于第 \(i\) 个人,如果他有至少一位邻居,则他的满意度为 \(a_i\);否则如果他没有邻居,则他的满意度为 \(b_i\)

您作为小区的规划者,需要最大化所有人的总满意度。

题解

可以发现最优解一定是:

AAA.B.B.B

或者:

B.B.B.B.B.B

或:

AAAAAA

其中 AB 分别表示有邻居的人和没有邻居的人。

让我们贪心地想,这里面的 A 一定是那些 \(b_i - a_i\) 最小的人,因为如果将 \(b_i - a_i\) 较大的人排为 A,把他放到 B 中对答案的贡献显然更大。

所以按照 \(b_i - a_i\) 从小到大排序,再枚举 \(i\) ( \(2\leqslant i\leqslant n - 1\) ),把 \(1 \sim i\) 当成有邻居的,\(i + 1 \sim n\) 当作没有邻居的,对这些情况取 \(\max\) 即可。

最后还要特判全是 A 和全是 B 的情况。

代码
#include <bits/stdc++.h>

using namespace std;

const int N = 5e5 + 5;
int n, m;
struct node {
  int a, b;
  inline bool operator < (const node &w) const {
    return (b - a) < (w.b - w.a);
  }
} a[N];

void solve() {
  cin >> n >> m;
  for (int i = 1; i <= n; ++i) cin >> a[i].a >> a[i].b;
  sort(a + 1, a + 1 + n);
  vector <long long> pre(n + 1, 0), suf(n + 2, 0);
  for (int i = 1; i <= n; ++i) pre[i] = pre[i - 1] + a[i].a;
  for (int i = n; i; --i) suf[i] = suf[i + 1] + a[i].b;
  long long ans = 0;
  for (int i = 0; i <= n; ++i) {
    if (i == 1) continue;
    int need = i + (n - i) * 2 - (!i);
    if (need <= m) ans = max(ans, pre[i] + suf[i + 1]);
  } cout << ans << '\n';
}

signed main(void) {
  ios :: sync_with_stdio(false);
  cin.tie(nullptr); cout.tie(nullptr);
  int T; cin >> T; while (T--) solve();
}

K

题目大意

独立钻石是一种单人桌游。游戏在 \(n\)\(m\) 列的棋盘上进行,棋盘上的每一格要么是空格,要么有一枚棋子。一开始,棋盘上共有 \(k\) 枚棋子。

在游戏中,玩家可以选择一枚棋子,将它跳过相邻棋子到空格上,并移除被跳过的棋子。具体来说,令 \((i, j)\) 表示位于第 \(i\) 行第 \(j\) 列的格子,玩家可以执行以下四种操作。

给定一个初始的棋盘,求经过任意次操作(包括零次)之后,棋盘上最少能剩余几枚棋子。

题解

由于 \(n, m, k\leqslant 6\),可以不用剪枝,直接搜索。

细节具体请看代码实现。

代码
#include <bits/stdc++.h>

using namespace std;

const int N = 7;

int n, m, k;
int a[N][N]; //a[i][j] 表示 (i, j) 的信息,如果为 0 表示这个格子为空,否则表示这个格子上有哪个棋子。
bool vis[N]; //vis[i] 表示第 i 个棋子是否被选过。

struct node {
  int x, y;
  node (int xx, int yy) {x = xx, y = yy;}
} ; //记录棋子坐标
vector <node> v;

int dx[4] = {0, 0, -1, 1}, dy[4] = {1, -1, 0, 0};
int ans = 0;

void dfs(int cnt) {
  ans = min(ans, cnt);
  for (int i = 0; i < k; ++i) if (!vis[i]) {
    for (int j = 0; j < 4; ++j) {
      int nx = v[i].x + dx[j], ny = v[i].y + dy[j];
      if (nx && ny && nx <= n && ny <= m && a[nx][ny] != 0) { 
        int tx = nx + dx[j], ty = ny + dy[j];
        if (tx && ty && tx <= n && ty <= m && a[tx][ty] == 0) {
          a[tx][ty] = i + 1; a[v[i].x][v[i].y] = 0;
          vis[a[nx][ny] - 1] = 1; 
          int nt = a[nx][ny]; a[nx][ny] = 0; 
          swap(v[i].x, tx); swap(v[i].y, ty); 
          dfs(cnt - 1);
          swap(v[i].x, tx); swap(v[i].y, ty);
          a[tx][ty] = 0; a[v[i].x][v[i].y] = i + 1;
          a[nx][ny] = nt; vis[i] = vis[a[nx][ny] - 1] = 0; //记得还原信息。
        }
      }
    }
  }
}

signed main(void) {
  ios :: sync_with_stdio(false);
  cin.tie(nullptr); cout.tie(nullptr);
  int T; cin >> T; while (T--) {
    cin >> n >> m >> k; ans = k;
    for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j)
      a[i][j] = 0; //多测要清空
    v.clear(); for (int i = 1; i <= k; ++i) {
      int x, y; cin >> x >> y;
      v.emplace_back(node(x, y));
      a[x][y] = i; vis[i - 1] = 0;
    } dfs(k); cout << ans << '\n';
  }
}

B

题目大意

中国移动通信集团广东有限公司深圳分公司(以下简称深圳移动)于 \(1999\) 年正式注册。四年后,广东省大学生程序设计竞赛第一次举办。深圳移动与广东省大学生程序设计竞赛一起见证了广东省计算机行业的兴旺与发展。

在建设通信线路的过程中,信号基站的选址是一个非常关键的问题。某城市从西到东的距离为 \(n\) 千米,工程师们已经考察了在从西往东 \(1, 2, \cdots, n\) 千米的位置建设基站的成本,分别是 \(a_1, a_2, \cdots, a_n\)

为了保证居民的通信质量,基站的选址还需要满足 \(m\) 条需求。第 \(i\) 条需求可以用一对整数 \(l_i\)\(r_i\) 表示(\(1 \le l_i \le r_i \le n\)),代表从西往东 \(l_i\) 千米到 \(r_i\) 千米的位置之间(含两端)至少需要建设 \(1\) 座基站。

作为总工程师,您需要决定基站的数量与位置,并计算满足所有需求的最小总成本。

题解

考虑 dp。

\(f_{i, j}\) 表示建立信号站使得所有 \(r_k\leqslant i\)\([l_k, r_k]\) 满足要求,且上一个信号站建立在 \(j\) 的最小花费。若 \(j = 0\),则表示未建立信号站。

答案为 \(\min\limits_{j = 0} ^ n f_{n, j}\)

考虑转移:

\(lmax_k = \max\limits_{1\leqslant i\leqslant m\land r_i = k} l_i\);若没有区间满足 \(r_i = k\)\(lmax_k = 0\)

设当前要转移的状态为 \(f_{i, j}\)

  • \(i = j\)\(f_{i, j} = \min\limits_{k = 0}^{i - 1} f_{i - 1, k}\)
  • 否则,若 \(lmax_i = 0\),那么 \(f_{i, j} = f_{i, j - 1}\)
  • 否则,\(f_{i, j} = \begin{cases} \inf & j < lmax_i\\ f_{i, j - 1} & lmax_i\leqslant j < i \end{cases}\)

可以发现转移需要查询前缀最小和区间赋值,可以用线段树优化,复杂度 \(\mathcal{O}(n \log n)\)

代码
#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 5e5 + 5, inf = 1e15;
int n, a[N], m;
int R[N];
struct node {
  int l, r;
  int cov, mi;
} t[N << 2];
int lson(int x) {return x << 1;}
int rson(int x) {return x << 1 | 1;}
void pushup(int x) {t[x].mi = min(t[lson(x)].mi, t[rson(x)].mi);}
void buildtree(int x, int l, int r) {
  t[x].l = l; t[x].r = r; t[x].cov = -1;
  if (l == r) {t[x].mi = (!l ? 0 : inf); return ;}
  int mid = (l + r) >> 1;
  buildtree(lson(x), l, mid);
  buildtree(rson(x), mid + 1, r);
  pushup(x);
}
void pushdown(int x) {
  if (~t[x].cov) {
    t[lson(x)].cov = t[lson(x)].mi = t[x].cov;
    t[rson(x)].cov = t[rson(x)].mi = t[x].cov;
    t[x].cov = -1;
  }
}
void update(int x, int L, int R, int v) {
  if (t[x].l >= L && t[x].r <= R) {
    t[x].cov = v;
    t[x].mi = v;
    return ;
  } int mid = (t[x].l + t[x].r) >> 1; pushdown(x);
  if (L <= mid) update(lson(x), L, R, v);
  if (R > mid) update(rson(x), L, R, v);
  pushup(x);
}
int query(int x, int L, int R) {
  if (t[x].l >= L && t[x].r <= R) return t[x].mi;
  int mid = (t[x].l + t[x].r) >> 1; pushdown(x); int res = inf;
  if (L <= mid) res = min(res, query(lson(x), L, R));
  if (R > mid) res = min(res, query(rson(x), L, R));
  return res;
}
signed main(void) {
  ios :: sync_with_stdio(false);
  cin.tie(nullptr); cout.tie(nullptr);
  int T; cin >> T; while (T--) {
    cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i], R[i] = 0;
    cin >> m; for (int i = 1; i <= m; ++i) {
      int l, r; cin >> l >> r;
      R[r] = max(R[r], l);
    }
    buildtree(1, 0, n);
    for (int i = 1; i <= n; ++i) {
      update(1, i, i, min(inf, query(1, 0, i - 1) + a[i]));
      if (R[i] > 0) update(1, 0, R[i] - 1, inf);
    }
    cout << query(1, 0, n) << '\n';
  }
}

F

题面大意

\(n\) 个格子排成一行,第 \(i\) 个格子的颜色为 \(c_i\),上面放置着一个权值为 \(v_i\) 的球。

您将要在格子中进行若干次旅行。每次旅行时,您会得到旅行的起点 \(x\) 与一个颜色集合 \(\mathbb{A} = \{a_1, a_2, \cdots, a_k\}\),且保证 \(c_x \in \mathbb{A}\)。旅行将从第 \(x\) 个格子上开始。在旅行期间,如果您在格子 \(i\) 处,那么您可以向格子 \((i - 1)\)\((i + 1)\) 处移动,但不能移动到这 \(n\) 个格子之外。且在任意时刻,您所处的格子的颜色必须在集合 \(\mathbb{A}\) 中。

当您位于格子 \(i\) 时,您可以选择将格子上的球取走,并获得 \(v_i\) 的权值。由于每个格子上只有一个球,因此一个格子上的球只能被取走一次。

您的任务是依次处理 \(q\) 次操作,每次操作形如以下三种操作之一:

  • \(1\; p \; x\):将 \(c_p\) 修改为 \(x\)
  • \(2\; p \; x\):将 \(v_p\) 修改为 \(x\)
  • \(3\; x\; k\; a_1\; a_2 \; \ldots\; a_k\):给定旅行的起点 \(x\) 与一个颜色集合 \(\mathbb{A} = \{a_1, a_2, \cdots, a_k\}\)。假设如果进行这样的一次旅行,求出取走的球的权值之和最大是多少。注意,由于我们仅仅假设进行一次旅行,因此并不会真的取走任何球。即,所有询问之间是独立的。

题解

可以发现旅行过程中一定能走就走,所以能拿到的球其实是一个区间。

考虑分别二分这个区间的左右端点。

需要判断一个区间是否由给出的颜色集合组成,队友当场写了一个 bitset,然后发现要 4 GB 空间,可以用平衡树来维护区间某种颜色的出现次数。

时间复杂度为 \(\mathcal{O}(\sum k\log^2 n)\),理论上 5s 能过,但是队友的 fhq-Treap 常数过大,被遗憾卡常。

赛后换了一个 WBLT,不知道能不能过,代码队友是写的。

posted @ 2023-10-03 19:43  CTHOOH  阅读(848)  评论(0)    收藏  举报