Gym-106648D [CCF CAT NAEC 2026] 卡牌接龙 题解

Gym-106648D [CCF CAT NAEC 2026] 卡牌接龙

对于一个整数序列 \(a_1,a_2,\cdots,a_m\) 中的位置 \(i\)(\(2\le i\le m\)),如果 \(a_{i-1}\) 整除 \(a_i\) 或 \(a_i\) 整除 \(a_{i-1}\),则称位置 \(i\) 是「Kirakira」的;特别地,规定位置 \(1\) 不是「Kirakira」的。

现在有 \(n\cdot k\) 个数,其中 \(1,2,\cdots,n\) 各有 \(k\) 个,且 \(k=\pi(n)\),即 \(k\) 为 \(1,2,\cdots,n\) 中的质数个数。

需要将这 \(n\cdot k\) 个数排成一个序列,使得「Kirakira」的位置尽可能多,请你构造并输出一个这样的序列。

\(2\le n\le5000\)。

设 \(x\) 和 \(y\) 的标准质因数分解为 \(x=\prod_{i=1}^\infty p_i^{\alpha_1}\),\(y=\prod_{i=1}^\infty p_i^{\beta_i}\),其中 \(\alpha_i,\beta_i\ge0\),那么 \(x\mid y\) 当且仅当 \(\alpha_i\le\beta_i\) 对所有 \(i=1,2,\cdots\) 都成立。

为了最大化利用这些数,容易想到序列的形式类似于 \(a_i,p_1a_i,a_i,p_2a_i,\cdots\)。

并且,注意到一个关键性质:由于 \(x\mid x\),如果这样构造之后 \(a_i\) 仍有剩余,可以将多余的 \(a_i\) 放在任何一个其已经出现的位置上。

那么,我们可以考虑让每个数出现次数尽可能不重复。

将乘一个质数 \(p\) 视为在图上走一步(有点类似于 AtCoder-arc084_b Small Multiple),即 \(x\to px\),那么为了保证 \(px\) 是新的点,一定要满足 \(p\ge\)「\(x\) 的最大质因数」。

这样一定不会重复经过点,那么就构成一棵树,根节点为 \(1\)。一个「Kirakira」位置最多的序列就是这棵树的一个欧拉序 (Euler tour)。

在欧拉序中,设节点 \(v\) 的子节点数量为 \(soncnt(v)\),则 \(v\) 出现的次数 \(c_v=soncnt(v)+1\)。

  • 对于 \(v=1\),\(c_1=\pi(n)+1\),所以我们只要去掉最后一个 \(1\) 即可。
  • 对于 \(v=2,3,\cdots\),由于子节点 \(pv\le n\),一定有 \(p\le n/v\),故可用的 \(p\) 的数量 \(\le\pi(\lfloor n/v\rfloor)\le\pi(\lfloor n/2\rfloor)<\pi(n)\)(对于 \(n\ge2\),在区间 \([\lfloor n/2\rfloor+1,n]\) 中至少有一个质数),从而 \(c_v=soncnt(v)+1<\pi(n)+1\),即 \(c_v\le\pi(n)\)。因此对于 \(v\ne1\) 都是够用的。

综上,我们得到了一种序列,使得每一对相邻位置都是「Kirakira」的。

Source code - Virtual Judge

#include <bits/stdc++.h>
#define f(i, a, b) for (int i = (a); i <= (b); ++i)
using namespace std;
typedef vector<int> vi;
typedef vector<vector<int> > vvi;

void solve() {
  int n, k; cin >> n >> k;
  vvi e(n + 1);
  vi pri, maxp(n + 1);
  bitset<5001> vis; // not prime
  vis[1] = true;
  f(i, 2, n) {
    if (!vis[i]) {
      pri.push_back(i);
      maxp[i] = i;
      e[1].push_back(i);
    }
    for (int j: pri) {
      if (i * j > n) break;
      vis[i * j] = true;
      maxp[i * j] = maxp[i];
      e[i * j / maxp[i]].push_back(i * j);
      if (i % j == 0) break;
    }
  }

  vi rem(n + 1, k);
  vi tour;
  auto dfs = [&](auto&& self, int u) -> void {
    tour.push_back(u);
    --rem[u];
    for (int v: e[u]) {
      self(self, v);
      tour.push_back(u);
      --rem[u];
    }
  };
  dfs(dfs, 1);

  tour.pop_back();
  for (int x: tour) {
    cout << x << ' ';
    while (rem[x] > 0) {
      cout << x << ' ';
      --rem[x];
    }
  }
  cout << '\n';
  return;
}

signed main() {
  cin.tie(0)->sync_with_stdio(false);

  int tt = 1;
  // cin >> tt;
  while (tt--) solve();

  return 0;
}
posted @ 2026-09-30 10:50  f2021ljh  阅读(7)  评论(0)    收藏  举报