2025/6/18 模拟赛

T1 . 序列与改写

  1. 对每个数单独计算其所有到“终止状态”的路径分布

    • 对于给定正整数 \(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\) 次改写到终止的路径数。

  2. 将各个数的路径分布通过“插入排列”方式合并

    • 序列中共有 \(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;
}


---

# 素数 

![](https://img2024.cnblogs.com/blog/3478174/202506/3478174-20250619071955082-846830841.png)


两次网络流,两次n^2暴力

先将奇数和偶数匹配

再将剩下的偶数和1匹配

再将1和1匹配

再将已标记的和未标记的匹配

。

---

# 拉丁方阵 

![](https://img2024.cnblogs.com/blog/3478174/202506/3478174-20250619072119071-1012102792.png)

如果Axy - Bxy = x,那么就将Axy + x,每个点都这么做。

证明:排列整体和不变,一次修改涉及2排列,减小x的话其他点必然会+2x来贡献回来。所以是对的。
posted @ 2025-06-19 07:22  Dreamers_Seve  阅读(16)  评论(0)    收藏  举报