2025/6/18 模拟赛
T1 . 序列与改写

-
对每个数单独计算其所有到“终止状态”的路径分布
-
对于给定正整数 \(a\),考虑所有满足条件的可改写子状态:即所有正约数 \(x<a\) 且二进制位上没有位使得 \(x\) 在该位是1而 \(a\) 在该位是0(即 \(x \,\&\,(\sim a)==0\))。由这些约数再递归向下,形成一个有向无环图(DAG),节点是满足条件的那些约数(以及自身),从大到小有边 \(y \to x\)。
-
在这个 DAG 中,终止状态是没有更小可改写子状态的节点。
-
我们对这个 DAG 做“从小到大”的 DP:设所有满足条件的约数按数值升序为 \(D[0..m-1]\),令 dp[i] 为一个向量,其中 dp[i][\(\ell\)] 表示从节点 \(D[i]\) 出发,需要恰好 \(\ell\) 步(即 \(\ell\) 次改写)到达某个终止节点的路径数。
- 若 \(D[i]\) 是终止节点(没有更小的合法子状态),则 dp[i] = {1},表示 0 步到终止(长度0路径数为1)。
- 否则,枚举所有子节点 \(D[j]\)(\(j<i\)、\(D[j]\) 是 \(D[i]\) 的合法改写),已知 dp[j],则对于每个 dp[j][L],从 \(D[i]\) 走一步到 \(D[j]\) 后再走 L 步到终止,合成长度 \(L+1\)。最终 dp[i][L+1] 累加所有子节点贡献。
-
最终我们取 dp[idx],其中 \(D[idx] = a\),得到一个向量 \(f\),其中 \(f[\ell]\) 表示从初始 \(a\) 恰好 \(\ell\) 次改写到终止的路径数。
-
-
将各个数的路径分布通过“插入排列”方式合并
-
序列中共有 \(n\) 个数。每个数 \(i\) 的改写过程长度为 \(\ell_i\),且该数有 \(f_i[\ell_i]\) 种选择。若固定每个数的路径长度 \(\ell_i\),则总的改写次数 \(K = \sum_i \ell_i\),不同元素的改写可以任意交错:总的交错方式数为多项式系数 \(\displaystyle \binom{K}{\ell_1,\ell_2,\dots,\ell_n} = \binom{K}{\ell_1}\binom{K-\ell_1}{\ell_2}\cdots\)。逐步合并时,也可用二元插入:
-
令全局 DP 向量 \(g\),初始 \(g[0]=1\)。按元素依次合并:假设当前累积的 DP 是 \(g[k]\) 表示已处理前几项,总改写次数正好为 \(k\) 的方案数。待合并新元素有分布 \(f[\ell]\)。合并后新 DP \(g'\) 满足:
\[ g'[k+\ell] \;+=\; g[k] \;\times\; f[\ell]\;\times\; \binom{k+\ell}{\ell}. \] -
最终,处理完所有元素后,答案是 \(\sum_{k} g[k]\)(因为不论总共用了多少次改写,都算一种过程)。
-
为支持 \(\binom{\cdot}{\cdot}\) 计算,先预估所有元素的最大可能改写长度之和,再预先计算阶乘和逆元阶乘(mod 998244353)。在本题中,每个 \(a_i\le10^6\),其合法子状态数目有限(约数个数一般较少),单个数的最长改写链长度也不会特别大,整体和一般在上万量级以内,所以预先按上界(例如 \(n\times 20\) 或实际累积最大长度)计算即可。
```cpp
#include <bits/stdc++.h>
using namespace std;
constexpr int MOD = 998244353;
constexpr int maxn = 2e6 + 10;
using ll = long long;
ll qpow(ll a, ll e = MOD - 2) {
ll r = 1;
while (e) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
constexpr int addmod(int a, int b) {
a += b;
return a >= MOD ? a - MOD : a;
}
constexpr int mulmod(ll a, ll b) {
return static_cast<int>((a * b) % MOD);
}
int a[maxn];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
vector<vector<int>> ff;
ff.reserve(n);
int maxsum = 0;
for (int idx = 0; idx < n; idx++) {
int v = a[idx];
vector<int> divs;
for (int d = 1; static_cast<ll>(d) * d <= v; d++) {
if (v % d == 0) {
divs.push_back(d);
if (d * d != v) divs.push_back(v / d);
}
}
ranges::sort(divs);
int m = static_cast<int>(divs.size());
unordered_map<int, int> id;
id.reserve(m * 2);
for (int i = 0; i < m; i++) {
id[divs[i]] = i;
}
vector<vector<int>> dp(m);
for (int i = 0; i < m; i++) {
int x = divs[i];
vector<int> son;
for (int j = 0; j < i; j++) {
int y = divs[j];
if (x % y != 0) continue;
if ((y & (~x)) != 0) continue;
son.push_back(j);
}
if (son.empty()) {
dp[i].push_back(1);
} else {
int maxlen = 0;
for (int j : son) {
int sz = static_cast<int>(dp[j].size());
if (sz > maxlen) maxlen = sz;
}
dp[i].assign(maxlen + 1, 0);
for (int j : son) {
for (int L = 0; L < static_cast<int>(dp[j].size()); L++) {
if (dp[j][L] == 0) continue;
int& tar = dp[i][L + 1];
tar = addmod(tar, dp[j][L]);
}
}
}
}
vector<int> f = dp[id[v]];
int res = static_cast<int>(f.size()) - 1;
if (res < 0) res = 0;
maxsum += res;
ff.push_back(std::move(f));
}
int N = maxsum + n + 5;
vector<ll> fac(N), invfac(N);
fac[0] = 1;
for (int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % MOD;
invfac[N - 1] = qpow(fac[N - 1]);
for (int i = N - 2; i >= 0; i--) invfac[i] = invfac[i + 1] * (i + 1) % MOD;
auto C = [&](int nn, int kk) -> int {
if (kk < 0 || kk > nn) return 0;
return static_cast<int>(fac[nn] * (invfac[kk] * invfac[nn - kk] % MOD) % MOD);
};
vector<int> g(1, 1);
int max1 = 0;
for (int i = 0; i < n; i++) {
auto& f = ff[i];
int res = static_cast<int>(f.size()) - 1;
if (res < 0) {
continue;
}
int new_max = max1 + res;
vector<int> ng(new_max + 1, 0);
for (int k = 0; k <= max1; k++) {
if (g[k] == 0) continue;
ll gv = g[k];
for (int l = 0; l <= res; l++) {
if (f[l] == 0) continue;
int ways = C(k + l, l);
ll add = gv * f[l] % MOD * ways % MOD;
ng[k + l] = addmod(ng[k + l], static_cast<int>(add));
}
}
g.swap(ng);
max1 = new_max;
}
ll ans = 0;
for (int x : g) ans = (ans + x) % MOD;
cout << ans << "\n";
return 0;
}
---
# 素数

两次网络流,两次n^2暴力
先将奇数和偶数匹配
再将剩下的偶数和1匹配
再将1和1匹配
再将已标记的和未标记的匹配
。
---
# 拉丁方阵

如果Axy - Bxy = x,那么就将Axy + x,每个点都这么做。
证明:排列整体和不变,一次修改涉及2排列,减小x的话其他点必然会+2x来贡献回来。所以是对的。
浙公网安备 33010602011771号