JOISC 2020 做题记录

JOISC 2020 做题记录

Day1 T1 Building 4

题意

有两个长度为 \(2n\) 的序列 \(a_1, \ldots, a_{2n}\)\(b_1, \ldots, b_{2n}\),构造一个长度为 \(2n\) 的序列 \(c\),要求 \(c\) 每个位置的值为 \(a, b\) 对应位置中选择一个,整体共有 \(n\) 个位置为 \(a\),另外 \(n\) 个位置选了 \(b\),且 \(c\) 单调不减。求一种方案。

\(n \le 5 \times 10 ^ 5\)

题解

首先有一个非常显然的 \(\mathcal{O}(n ^ 2)\) 的 DP 做法。记 \(f(i, j, 0/1)\) 表示只考虑了前 \(i\) 个位置,选了 \(j\)\(a\) ,第 \(i\) 位选了 \(a\) / \(b\) 的是否可能,直接转移。还原方案路径还原即可。

考虑优化,不难猜到一个结论:对于一个确定的 \(i\),所有使得 \(f(i, j, 0/1)\) 值为真的 \(j\),一定是一段连续的区间。

一种不严谨的证明是,我们考虑更换 DP 的方式。我们定义另一个 DP,\(g(i, 0/1)\) 表示只考虑了前 \(i\) 个位置,第 \(i\) 位选了 \(a\) / \(b\) 的,使得值为真的 \(j\) 的集合。

然后把这个状态转移的显式图给画出来,令 \(A_i\) 表示 \(g(i, 0)\) 对应的点,\(B_i\) 表示 \(g(i, 1)\) 对应的点。能够发现,如果 \(j\) 不是一段连续的区间,则必然需要存在一个位置 \(x\),满足 \(A_x\)\(A_{x + 1}\) 连边(即 \(a_x \le a_{x + 1}\)),\(B_x\)\(B_{x + 1}\) 连边,\(A_x\) 不能\(B_{x + 1}\) 连边,\(B_x\) 不能\(A_{x + 1}\) 连边。

然而这是不可能的。分类讨论一下 \(a_x\)\(b_x\) 的大小关系。若 \(a_x \le b_x\),则 \(a_x \le b_x \le b_{x + 1}\),说明 \(A_x\) 有向 \(B_{x + 1}\) 连边;若 \(b_x \lt a_x\),则 \(b_x \lt a_x \le a_{x + 1}\),说明 \(B_x\) 有向 \(A_{x + 1}\) 连边,都与条件矛盾。于是结论得证。

知道可行的 \(j\) 是一个区间就简单了,用和 \(g\) 一样的 DP 方法,区间只要存左右端点就好了,就优化掉了一个 \(n\)

时间复杂度 \(\mathcal{O}(n)\),空间复杂度 \(\mathcal{O}(n)\)

代码

#include <algorithm>
#include <cstdio>
#include <cstring>

const int MaxN = 1000000;

int N;
int A[MaxN + 5], B[MaxN + 5];
int L[MaxN + 5][2], R[MaxN + 5][2];
bool F[MaxN + 5][2];
int Ans[MaxN + 5];

void init() {
  scanf("%d", &N);
  N <<= 1;
  for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
  for (int i = 1; i <= N; ++i) scanf("%d", &B[i]);
}

void solve() {
  L[0][0] = L[0][1] = R[0][0] = R[0][1] = 0;
  F[0][0] = F[0][1] = true;
  for (int i = 1; i <= N; ++i) {
    L[i][0] = N + 1, R[i][0] = -1;
    L[i][1] = N + 1, R[i][1] = -1;
    if (A[i] >= A[i - 1] && F[i - 1][0] == true) {
      L[i][0] = std::min(L[i][0], L[i - 1][0] + 1);
      R[i][0] = std::max(R[i][0], R[i - 1][0] + 1);
      F[i][0] = true;
    }
    if (A[i] >= B[i - 1] && F[i - 1][1] == true) {
      L[i][0] = std::min(L[i][0], L[i - 1][1] + 1);
      R[i][0] = std::max(R[i][0], R[i - 1][1] + 1);
      F[i][0] = true;
    }
    if (B[i] >= A[i - 1] && F[i - 1][0] == true) {
      L[i][1] = std::min(L[i][1], L[i - 1][0]);
      R[i][1] = std::max(R[i][1], R[i - 1][0]);
      F[i][1] = true;
    }
    if (B[i] >= B[i - 1] && F[i - 1][1] == true) {
      L[i][1] = std::min(L[i][1], L[i - 1][1]);
      R[i][1] = std::max(R[i][1], R[i - 1][1]);
      F[i][1] = true;
    }
  }
  int ok = -1;
  if (F[N][0] == true && L[N][0] <= N / 2 && N / 2 <= R[N][0]) ok = 0;
  if (F[N][1] == true && L[N][1] <= N / 2 && N / 2 <= R[N][1]) ok = 1;
  if (ok == -1) puts("-1");
  else {
    int last = N / 2;
    for (int i = N; i >= 1; --i) {
      Ans[i] = ok;
      if (ok == 0) {
        last--;
        if (F[i - 1][0] == true && A[i - 1] <= A[i] && L[i - 1][0] <= last && last <= R[i - 1][0]) ok = 0;
        else ok = 1;
      } else {
        if (F[i - 1][0] == true && A[i - 1] <= B[i] && L[i - 1][0] <= last && last <= R[i - 1][0]) ok = 0;
        else ok = 1;
      }
    }
    for (int i = 1; i <= N; ++i)
      putchar("AB"[Ans[i]]);
    putchar('\n');
  }
}

int main() {
  init();
  solve();
  return 0;
}

Day1 T2 Hamburg Steak

题意

平面上有 \(n\) 个矩形,需要选出 \(k\) 个点,使得每个矩形内至少有一个点。数据保证有解。

\(n \le 2 \times 10 ^ 5\)\(k \le 4\)

题解

不会,先咕着。

Day1 T3 Sweeping

题意

\(n \times n\) 的平面上,初始有 \(m\) 个点,进行 \(q\) 次操作。操作有以下四种:

  • 询问一个点的位置。
  • 给出参数 \(l\),把所有 \((x, y)\) 满足 \(x \lt n - l, y \le l\) 的点移到 \((n - l, y)\)
  • 给出参数 \(l\),把所有 \((x, y)\) 满足 \(x \le l, y \lt n - l\) 的点移到 \((x, n - l)\)
  • 加入一个点。

\(n \le 10 ^ 9\)\(m \le 5 \times 10 ^ 5\)\(q \le 10 ^ 6\)

题解

不会,先咕着。

Day2 T1 Chameleon’s Love

题意

这是一道交互题。

\(2n\) 只变色龙,\(n\) 只为雄性,\(n\) 只为雌性。每只变色龙有一个初始颜色 \(c_i\),满足对于雄性和雌性内部来说,\(c_i\) 都是一个 \(1 \sim n\) 的排列。此外,每只变色龙还有喜欢的对象 \(l_i\)

你可以举办不超过 \(20\ 000\) 次派对。在派对中,对于一名与会变色龙 \(x\),若 \(l_x\) 同样与会,则它的颜色会是 \(c_{l_x}\),否则颜色是 \(c_x\)。你可以得到与会变色龙颜色的种类数。请确定每对颜色相同的变色龙。

\(n \le 500\)

题解

不会,先咕着。

Day2 T2 Making Friends on Joitter is Fun

题意

给出一张 \(n\) 个点的图,进行 \(m\) 次操作,每次操作加入一条有向边 \((u, v)\)

然后对图进行维护,若存在三元组 \((x, y, z)\),满足存在有向边 \((x, y), (y, z), (z, y)\),则加入有向边 \((x, z)\)。一直操作直到不能增加新边为止。

求每一步后图上的边数。

\(n \le 10 ^ 5\)\(m \le 3 \times 10 ^ 5\)

题解

不会,先咕着。

正在补了。

Day2 T3 Ruins 3

题意

\(2n\) 座石柱,初始时对于每个 \(x ~ (1 \le x \le n)\),都有两座石柱高度为 \(x\),接着发生了 \(n\) 次地震,第 \(i\) 次地震会使得所有的高度不为 \(0\) 的石柱 \(j\),设其原本高度为 \(h_j\),若满足存在一个 \(k\) 使得 \(h_j = h_k\),石柱 \(j\) 的高度就会减一。

最终只剩下了 \(n\) 座石柱,给出这些石柱在原始序列中对应的下标,求能形成这样的初始方案数。对 \(10 ^ 9 + 7\) 取模。

\(n \le 600\)

题解

不会,先咕了。

Day3 T1 Constellation 3

题意

有一个 \(n \times n\) 的平面,平面上有若干个格子被染黑,其中第 \(i\) 列被染黑的格子为第 \(1\) 行开始到第 \(a_i\) 行的所有格子。

在没被染黑的格子中,有若干关键格。每个关键格有一个价值 \(c_i\)

你需要删掉一些关键格,使得平面上不存在一个矩形,使得这个矩形内部没有黑色格子,且有两个以上的关键格。代价为删掉关键和价值之和。求最小代价。

\(n, m \le 2 \times 10 ^ 5\)

题解

首先把所有的楼房往左右推,只保留其左端右端的墙,即第 \(i\) 列与 \(i + 1\) 列间的墙高为 \(\max(a_i, a_{i + 1})\)。现在变成要求矩形内不能有墙。

把序列抽象成一条长度为 \(n\) 的链,连接 \(i\) 号点和 \(i+1\) 号点的值为之间的墙高,即 \(\max(a_i, a_{i + 1})\)。然后建大根 Kruskal 重构树。

对于每一颗星星,我们找到它最浅的祖先,满足 \(weight_f \lt y\),这就保证了 \(fa_f \ge y\),这个类似 NOI 2018 归程的做法,可以倍增找。

把答案转化成所有星星 - 选中星星的和最大值。

然后设计 \(f_i\) 表示在子树 \(i\) 内,加入了所有权值不超过 \(weight_{fa(i)}\) 的星星(也就是不会与将要合并的区间冲突)的最大值。

\(u\) 上做合并的时候,一种是不做特别操作,\(f_u = f_{lson(u)} + f_{rson(u)}\)

另一种是,可能会有一些星星要在 \(u\) 上加入,也就是前面找到的那个最浅的祖先的用处。

加入一颗星星的时候,把树画出来,会发现它的答案是 这颗星星的 \(c\) 加上 一段从叶子到 \(u\) 的路径上(所有不在路径上的儿子)的 \(f\) 值之和。

这个用树链剖分可以简单维护,就是每个节点存下重儿子的 \(f\) 值,在轻重链切换时单独计算一下轻儿子的值。根据一些性质在重链上记后缀和优化到一个 \(\log\)

时间复杂度 \(\mathcal{O}(n \log n + m \log n)\)

代码

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>

const int MaxN = 200000, MaxM = 200000;
const int MaxV = 400000, MaxLog = 19;

struct edge_t {
  int u, v, w;
  edge_t(int _u = 0, int _v = 0, int _w = 0) { u = _u, v = _v, w = _w; }
  inline friend bool operator<(const edge_t &a, const edge_t &b) { return a.w < b.w; }
};

struct star_t {
  int x, y, c;
  star_t(int _x = 0, int _y = 0, int _c = 0) { x = _x, y = _y, c = _c; }
};

int N, M, V;
long long Sum;
int Height[MaxN + 5];
star_t A[MaxM + 5];
int Par[MaxV + 5], Fa[MaxLog + 1][MaxV + 5], Dep[MaxV + 5], Weight[MaxV + 5], Lson[MaxV + 5], Rson[MaxV + 5];
int Siz[MaxV + 5], Wson[MaxV + 5], Top[MaxV + 5];
std::vector<int> Vec[MaxV + 5];
long long F[MaxV + 5], Suf[MaxV + 5];

void init() {
  scanf("%d", &N);
  for (int i = 1; i <= N; ++i) scanf("%d", &Height[i]);
  N++; Height[N] = N;
  scanf("%d", &M);
  for (int i = 1; i <= M; ++i) {
    scanf("%d %d %d", &A[i].x, &A[i].y, &A[i].c);
    Sum += A[i].c;
  }
}

int find(int x) { return x == Par[x] ? x : Par[x] = find(Par[x]); }

void dfs(int u) {
  Siz[u] = 1;
  if (u <= N) return;
  Dep[Lson[u]] = Dep[u] + 1;
  Fa[0][Lson[u]] = u;
  for (int i = 1; (1 << i) <= Dep[Lson[u]]; ++i)
    Fa[i][Lson[u]] = Fa[i - 1][Fa[i - 1][Lson[u]]];
  Dep[Rson[u]] = Dep[u] + 1;
  Fa[0][Rson[u]] = u;
  for (int i = 1; (1 << i) <= Dep[Rson[u]]; ++i)
    Fa[i][Rson[u]] = Fa[i - 1][Fa[i - 1][Rson[u]]];
  dfs(Lson[u]), dfs(Rson[u]);
  Siz[u] += Siz[Lson[u]] + Siz[Rson[u]];
  if (Siz[Lson[u]] > Siz[Rson[u]]) Wson[u] = Lson[u];
  else Wson[u] = Rson[u];
}

void dfs(int u, int chain) {
  Top[u] = chain;
  if (u <= N) return;
  dfs(Wson[u], chain);
  dfs(Wson[u] == Lson[u] ? Rson[u] : Lson[u], Wson[u] == Lson[u] ? Rson[u] : Lson[u]);
}

inline int binSearch(int u, int val) {
  for (int i = MaxLog; i >= 0; --i) {
    if (Fa[i][u] == 0) continue;
    if (Weight[Fa[i][u]] < val) u = Fa[i][u];
  }
  return u;
}

inline long long queryPath(int u, int f) {
  long long res = 0;
  while (Top[u] != Top[f]) {
    res += Suf[Top[u]] - Suf[u];
    u = Fa[0][Top[u]];
    res += F[Wson[u]];
  }
  res += Suf[f] - Suf[u];
  return res;
}

void bfs() {
  static int que[MaxV + 5], ind[MaxV + 5];
  int head = 1, tail = 0;
  for (int i = N + 1; i <= V; ++i) ind[i] = 2;
  for (int i = 1; i <= N; ++i) que[++tail] = i;
  while (head <= tail) {
    int u = que[head++];
    if (u > N) {
      F[u] = F[Lson[u]] + F[Rson[u]];
      Suf[u] += Suf[Wson[u]];
    }
    for (int id : Vec[u]) {
      int x = A[id].x, c = A[id].c;
      F[u] = std::max(F[u], queryPath(x, u) + c);
    }
    if (u != V && Wson[Fa[0][u]] != u) Suf[Fa[0][u]] += F[u];
    ind[Fa[0][u]]--;
    if (ind[Fa[0][u]] == 0) que[++tail] = Fa[0][u];
  }
}

void solve() {
  static edge_t e[MaxN + 5];
  for (int i = 1; i < N; ++i) e[i] = edge_t(i, i + 1, std::max(Height[i], Height[i + 1]));
  std::sort(e + 1, e + N);
  for (int i = 1; i <= N; ++i) Par[i] = i;
  V = N;
  for (int i = 1; i < N; ++i) {
    int u = e[i].u, v = e[i].v, w = e[i].w;
    int p = find(u), q = find(v);
    V++;
    Par[p] = Par[q] = Par[V] = V;
    Weight[V] = w;
    Lson[V] = p, Rson[V] = q;
  }
  dfs(V), dfs(V, V);
  for (int i = 1; i <= M; ++i) {
    int f = binSearch(A[i].x, A[i].y);
    Vec[f].push_back(i);
  }
  bfs();
  printf("%lld\n", Sum - F[V]);
}

int main() {
  init();
  solve();
  return 0;
}

Day3 T2 Harvest

题意

有一个长度为 \(L\) 的环,有 \(N\) 名员工,\(M\) 棵果树,第 \(i\) 名员工位于 \(A_i\),第 \(i\) 棵果树位于 \(B_i\)\(A_i,B_i\)\(N+M\) 个数两两不同。

每秒,每名员工会向 \(+1\) 移动一格。一个员工如果到达了一棵苹果树,并且树上有苹果,他就会采下一个苹果。采苹果时间忽略不计。

每棵苹果树上最多只会有一个苹果。如果一个苹果被采下,苹果树要花费 \(C\) 的时间才会长出一个新苹果。

\(Q\) 次询问,每次询问给出两个数 \(V_i, T_i\),询问对于第 \(V_i\) 号员工,进行 \(T_i\) 时间后,采了多少苹果。

\(L, C \le 10 ^ 9\)\(N, M \le 2 \times 10 ^ 5\)\(T_i \le 10 ^ {18}\)

题解

不会,先咕了。

Day3 T3 Stray Cat

题意

这是一道通信题。有两个人 Anthony 和 Catherine,需要分别实现他们的程序。

给出一张 \(n\) 个点,\(m\) 条边的图。Anthony 可以在边上放置标记,标记一共有 \(a\) 种。Catherine 一开始在某个节点,她需要通过 Anthony 提前放置的标记获取信息,在多余步数不超过 \(b\) 步内到达 \(0\) 号节点。

Catherine 获取的信息是,对于当前的点,除了走来的那条边以外,每种标记对于的其它边的数量。她可以选择一个颜色走,也可以沿着刚来的那条边往回走。

请设计 Anthony 和 Catherine 的策略。

\(n, m \le 20\ 000\)

有两类数据:

  • \(a = 3, b = 0\)
  • \(a = 2, m = n - 1, b = 6\)

题解

分两类解决。

第一类 \(a = 3, b = 0\)

注意到 \(\binom{3}{2} = 3\),我们可以根据相邻的颜色种类数来确定哪一条是应当走的边。我们不妨令 Catherine 的策略为,若相邻的颜色只有一种,就走那一种;否则相邻的颜色为两种,若为 \(0, 1\),选择颜色 \(0\),若为 \(1, 2\),选择颜色 \(1\),若为 \(2, 0\),选择颜色 \(0\)。接下来构造 Anthony 的方案使他能够正确引导凯瑟琳。

\(0\) 号点开始跑原图的最短路,建出最短路树。根据三角形不等式,对于原来的每条边,要么连接相邻的两层,要么连接同一层内的两点。我们令连接相邻两层的边颜色按照深度依次为 \(0, 1, 2, 0, 1, 2, \ldots\),连接同一层内的边颜色为该层连向下一层的边的颜色以保证 Catherine 永远不会走。

这样就可以正确引导了,得分 \(15\) 分。

第二类 \(a = 2, m = n - 1, b = 6\)

猜你喜欢

转载自www.cnblogs.com/tweetuzki/p/12572031.html