2025.8.22 NOI 模拟赛 题解

T1

题意

给定 \(a_{1\sim n}\),一次操作 \((x,y)\)\(a_x\gets \gcd(a_x,a_y)\),求令所有 \(a\) 都相同的最小操作次数,\(n,a_i\le4\times10^6\)\(0.5s\)

分析

先令所有 \(a_i\) 都除以 \(\gcd_i a_i\),这样最终需要令所有 \(a_i\) 都变为 \(1\)

若已经存在 \(1\),则答案为 \(1-\sum_i [a_i=1]\)

否则显然最优策略为从 \(\{a\}\) 中选择 \(r\)\(\gcd\) 起来得到 \(1\),然后用这个 \(1\) 对剩余 \(a\) 分别进行一次 \(\gcd\),操作次数为 \(n+r-2\),显然需要最小化 \(r\)

预处理 \(c_i\) 表示 \(a\)\(i\) 的倍数的数量,容易狄利克雷后缀和做到 \(O(V\ln\ln V)\)(令 \(V=\max a\)

假如已经选择了一个 \(a_i\),它还有 \(\omega(a_i)\) 个质因子需要消除,选择一个 \(a_j\)\(a_i\gets (a_i,a_j)\),要么不产生影响,此时显然 \(a_j\) 无效,要么 \(a_i\) 删去至少一个质因子(由于此时剩下的 \(a\)\(\gcd\)\(1\),因此一定存在),因此答案上限为 \(1+\max_{n\le V} \omega(n)\le O\left(\frac{\ln V}{\ln\ln V}\right)\)

二分答案,设二分值为 \(M\),令 \(g_i\) 表示选择 \(M\) 个数,它们 \(\gcd\)\(i\) 的方案数,显然等于 \(f_i=\binom{c_i}M\) 二项式反演的结果,可得 \(g_1=\sum_{1\le i\le V}\mu(i)\binom{c_i}M\)

总时间复杂度 \(O\left(n+V\ln\ln V+V\log\left(\frac{\ln V}{\ln\ln V}\right)\right)=O(n+V\ln\ln V)\)

代码:

//utf-8  c++14
#include <cstdint>
#include <type_traits>
#include <limits>
#include <iterator>
#include <numeric>
#include <functional>
#include <iostream>
#include <random>

namespace modint_nm {

    using u32 = std::uint32_t;
    using u64 = std::uint64_t;
    using umx = std::uintmax_t;
    using i32 = std::int32_t;
    using imx = std::intmax_t;

    template<bool _Cond, typename _Tp = void>
        using enable_if_t = typename std::enable_if<_Cond, _Tp>::type;

    template <u32 mod, typename type, typename = void> struct modval_t
        { static inline u32 modval(type v) noexcept { return v % mod; } };
    template <u32 mod, typename type> struct modval_t <mod, type, enable_if_t<std::is_unsigned<type>::value> >
        { static inline u32 modval(type v) noexcept { return v % mod; } };
    template <u32 mod, typename type> struct modval_t <mod, type, enable_if_t<std::is_signed<type>::value> >
        { static inline u32 modval(type v) noexcept { v %= i32(mod);return v < 0? v + mod : v; } };
    template <u32 mod, typename type> struct modval_t <mod, type, enable_if_t<std::is_floating_point<type>::value> >
        { static inline u32 modval(type v) noexcept { return modval_t<mod, imx>::modval(imx(v)); } };

    template <u32 mod> inline auto modsplus(u32 &a, u32 b) noexcept
        -> enable_if_t<(std::numeric_limits<u32>::max() / 2 + 1 < mod), void> { if (a >= mod - b)a -= mod - b;else a += b; }
    template <u32 mod> inline auto modsplus(u32 &a, u32 b) noexcept
        -> enable_if_t<std::numeric_limits<u32>::max() / 2 + 1 >= mod, void> { a += b;if (a >= mod)a -= mod; }
    template <u32 mod> inline auto modplus(u32 a, u32 b) noexcept
        -> enable_if_t<(std::numeric_limits<u32>::max() / 2 + 1 < mod), u32> { return a >= mod - b? a - (mod - b) : a + b; }
    template <u32 mod> inline auto modplus(u32 a, u32 b) noexcept
        -> enable_if_t<std::numeric_limits<u32>::max() / 2 + 1 >= mod, u32> { a += b;return a >= mod? a - mod : a; }

    template <u32 mod> inline void modssubt(u32 &a, u32 b) noexcept { if (a < b)a += mod - b; else a -= b; }
    template <u32 mod> inline u32 modsubt(u32 a, u32 b) noexcept { return a < b? a + (mod - b) : a - b; }

    template <u32 mod> inline u32 modmult(u32 a, u32 b) noexcept { return u64(a) * b % mod; }
    template <u32 mod> inline void modsmult(u32 &a, u32 b) noexcept { a = modmult<mod>(a, b); }

    template <u32 mod> inline u32 modfpow(u32 a, umx b) noexcept
        { u32 ret = 1;for (; b; b >>= 1, a = modmult<mod>(a, a))if (b & 1)ret = modmult<mod>(ret, a);return ret; }
    template <u32 mod> inline void modsfpow(u32 &a, umx b) noexcept { a = modfpow<mod>(a, b); }

    // 假定值在模范围内,减少一次取模操作
    struct in_mod_range_t { } in_mod_range;

    struct modint_tag { };

    template <u32 mod_>
        struct modint {
            typedef modint_tag is_modint_t;
            static constexpr u32 mod = mod_;
            u32 val;
            inline modint() noexcept : val{} {}
            template <typename Val_t> inline modint(Val_t Val) noexcept : val(modval_t<mod, Val_t>::modval(Val)) {}
            inline modint(const modint &o) : val{o.val} {}
            inline modint(const modint &&o) : val{o.val} {}
            inline modint(in_mod_range_t, u32 v) noexcept : val{v} {}
            template <typename Val_t> inline modint &operator = (Val_t Val) noexcept
                { val = modval_t<mod, Val_t>::modval(Val);return *this; }
            inline modint &operator = (modint o) noexcept { val = o.val;return *this; }
            inline modint &operator += (modint o) noexcept { modsplus<mod>(val, o.val);return *this; }
            inline modint &operator -= (modint o) noexcept { modssubt<mod>(val, o.val);return *this; }
            inline modint &operator *= (modint o) noexcept { modsmult<mod>(val, o.val);return *this; }
            inline modint &operator ^= (u64 o) noexcept { modsfpow<mod>(val, o);return *this; }
            inline modint &operator ++ () noexcept {if (__builtin_expect(++val == mod, false))val = 0;return *this;}
            inline modint operator ++ (int) noexcept {auto R = *this;if (__builtin_expect(++val == mod, false))val = 0;return R;}
            inline modint &operator -- () noexcept {if (__builtin_expect(--val == u32(-1), false))val = mod - 1;return *this;}
            inline modint operator -- (int) noexcept {auto R = *this;if (__builtin_expect(--val == u32(-1), false))val = mod - 1;return R;}
            inline modint operator - () const noexcept { return modint(in_mod_range, val? mod - val : 0u); }
            inline modint inv() const noexcept { return (*this) ^ (mod - 2); }
            inline modint &operator /= (modint o) noexcept { modsmult<mod>(val, o.inv().val);return *this; }
            inline u32 value() const noexcept { return val; }
            inline operator u32 () const noexcept { return val; }
            template <typename _Tp> inline operator _Tp () const { return (_Tp)val; }

            static constexpr modint nan = modint(in_mod_range, mod);

        };

    template <u32 mod> inline modint<mod> operator + (modint<mod> a, modint<mod> b) noexcept
        { return modint<mod>(in_mod_range, modplus<mod>(a.val, b.val)); }
    template <u32 mod, typename _T> inline modint<mod> operator + (_T a, modint<mod> b) noexcept { return modint<mod>(a) + b; }
    template <u32 mod, typename _T> inline modint<mod> operator + (modint<mod> a, _T b) noexcept { return a + modint<mod>(b); }

    template <u32 mod> inline modint<mod> operator - (modint<mod> a, modint<mod> b) noexcept
        { return modint<mod>(in_mod_range, modsubt<mod>(a.val, b.val)); }
    template <u32 mod, typename _T> inline modint<mod> operator - (_T a, modint<mod> b) noexcept { return modint<mod>(a) - b; }
    template <u32 mod, typename _T> inline modint<mod> operator - (modint<mod> a, _T b) noexcept { return a - modint<mod>(b); }

    template <u32 mod> inline modint<mod> operator * (modint<mod> a, modint<mod> b) noexcept
        { return modint<mod>(in_mod_range, modmult<mod>(a.val, b.val)); }
    template <u32 mod, typename _T> inline modint<mod> operator * (_T a, modint<mod> b) noexcept { return modint<mod>(a) * b; }
    template <u32 mod, typename _T> inline modint<mod> operator * (modint<mod> a, _T b) noexcept { return a * modint<mod>(b); }

    template <typename _Tp, typename = void> struct is_modint : std::false_type { };
    template <typename _Tp> struct is_modint <_Tp, typename _Tp::is_modint_t> : std::true_type { };

    template <typename iter_t>
        void calc_inv(u32 n, iter_t iv){//let iv_i = i^{-1}, 1<=i<n
            constexpr u32 mod = std::iterator_traits<iter_t>::value_type::mod;
            iv[1] = 1;
            for (u32 i = 2; i < n; ++i)iv[i] = iv[mod % i] * modint<mod>(in_mod_range, mod - mod / i);
        }

    template <typename iter_t>
        void calc_fact(u32 n, iter_t fc){//let fc_i = i!, 0<=i<n
            *fc = 1;
            for (u32 i = 1; i < n; ++i)fc[i] = fc[i - 1] * i;
        }

    template <typename iter_t>
        void calc_factinv(u32 n, iter_t ivfc){
            calc_inv(n, ivfc);
            ivfc[0] = 1;
            for (u32 i = 1; i < n; ++i)ivfc[i] *= ivfc[i - 1];
        }

    template <u32 mod>
        struct binom_solver {
            u32 n;
            modint<mod> *fc, *ivfc;
            inline binom_solver() : n(0), fc(new modint<mod>[0]), ivfc(new modint<mod>[0]) {}
            inline binom_solver(u32 __n) : n(__n), fc(new modint<mod>[n]), ivfc(new modint<mod>[n])
                { calc_fact(n, fc), calc_factinv(n, ivfc); }
            inline void build(u32 __n){
                if (__n < n)return;
                delete [] fc;delete [] ivfc;
                n = __n, fc = new modint<mod>[n], ivfc = new modint<mod>[n];
                calc_fact(n, fc), calc_factinv(n, ivfc);
            }
            inline ~binom_solver() { delete [] fc;delete [] ivfc; }
            inline modint<mod> combination(i32 n, i32 m) const //C(n,m)
                { return ((n - m) | m) >> 31? modint<mod>() : fc[n] * ivfc[m] * ivfc[n - m]; }
        };

}

#include <bits/stdc++.h>
using namespace std;
constexpr unsigned M = 998244353;
using mod = modint_nm::modint<M>;
modint_nm::binom_solver<M> Cm;
constexpr unsigned MXSZ = 1 << 20;char buf[MXSZ], *p1, *p2;
#ifdef testing
#define gc getchar()
#else
#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MXSZ, stdin), p1 == p2)? EOF : *p1++)
#endif
int read(){
    int x = 0;bool f = false;int c = gc;for (; !isdigit(c); c = gc)if (c == '-')f = true;
    for (; isdigit(c); c = gc)x = (x << 1) + (x << 3) + (c ^ 48);return f? -x : x;
}
int n, a[4000010];
int c[4000010];//cnt multiple
int mu[4000010];
vector<int> sieve(int n){
    static bitset<4000010> npr;
    npr.reset();
    vector<int> ret;
    mu[1] = 1;
    for (int i = 2; i <= n; ++i){
        if (!npr.test(i))ret.emplace_back(i), mu[i] = -1;
        for (int p : ret){
            if (p * i > n)break;
            npr.set(p * i);
            if (i % p == 0){mu[p * i] = 0;break;}
            mu[p * i] = -mu[i];
        }
    }
    return ret;
}
int main(){
    freopen("a.in", "r", stdin);
    freopen("a.out", "w", stdout);
    n = read();
    generate_n(a + 1, n, read);
    int gd = accumulate(a + 1, a + n + 1, 0,
        +[](unsigned a, unsigned b){
            if (!a | !b)return a | b;
            int az = __builtin_ctz(a), bz = __builtin_ctz(b), z = az > bz? bz : az;b >>= bz;unsigned tmp = 0;
            do {a >>= az;tmp = b - a;az = __builtin_ctz(tmp);if (a < b)b = a, a = tmp;else a = -tmp;} while (a);
            return b << z;
        });
    for (int i = 1; i <= n; ++i)++c[a[i] /= gd];
    if (c[1]){
        cout << n - c[1] << endl;
        return 0;
    }
    int v = *max_element(a + 1, a + n + 1);
    for (int p : sieve(v))
        for (int i = v / p; i; --i)c[i] += c[i * p];
    Cm.build(n + 1);
    auto C = [&](int n, int m){return Cm.combination(n, m);};
    int l = 1, r = min(n, __lg(v) + 1), rs = -1;
    while (l <= r){
        int M = l + r >> 1;
        auto chk = [&](int M){
            mod r = 0;
            for (int i = 1; i <= v; ++i)r += mod(mu[i]) * C(c[i], M);
            return r.value() > 0;
        };
        if (chk(M))r = M - 1, rs = M;
        else l = M + 1;
    }
    cout << n + rs - 2 << endl;
    return 0;
}

T2

题意

给定两个长度为 \(n\) 的环 \(a_{1\sim n}\)\(b_{1\sim n}\),将 \(b\) 分割为若干部分(可以不分割),每部分内部任意重排,求能使得 \(a\)\(b\) 相等(不能旋转)的分割方案数,\(n,a_i,b_i\le10^6\)

分析

显然无解当且仅当 \(a\)\(b\) 构成的可重集不同

对于每个 \(1\le i\le n\),记录一个 \(V\) 元组 \((c_1,c_2,\cdots,c_v)\),其中 \(c_x\) 表示当前前缀中 \(a\)\(x\) 的数量减去 \(b\)\(x\) 的数量,显然 \([l,r]\) 可以作为一段当且仅当 \(l-1\)\(r\)\(V\) 元组相同

显然同时只能断开同一等价类中的位置

对于每个 \(N\) 元组的等价类,设其大小为 \(S\),则对答案的贡献为 \(2^S-1\)(不能全都不选),再加上不断开的方案数为 \(1\),容易得到答案

用和哈希保存 \(N\) 元组即可,时间复杂度 \(O(n+V)\)

代码:

#include <bits/stdc++.h>
using namespace std;
mt19937_64 mt(random_device{}());
using hsh_t = mt19937_64::result_type;
int n, a[1000010], b[1000010];
hsh_t h[1000010];
hsh_t s[1000010];
constexpr unsigned M = 998244353;
inline unsigned add(unsigned a, unsigned b){return a += b, a >= M? a - M : a;}
inline unsigned mul(unsigned a, unsigned b){return 1ull * a * b % M;}
inline unsigned pw2(int b){unsigned a = 2, r = 1;for (; b; b >>= 1, a = mul(a, a))if (b & 1)r = mul(r, a);return r;}
int main(){
    freopen("b.in", "r", stdin);
    freopen("b.out", "w", stdout);
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; ++i)cin >> a[i];
    for (int i = 1; i <= n; ++i)cin >> b[i];
    generate_n(h, max(*max_element(a + 1, a + n + 1), *max_element(b + 1, b + n + 1)) + 1, mt);
    unordered_map<hsh_t, int> cnt;
    for (int i = 1; i <= n; ++i)
        s[i] = s[i - 1] + (h[a[i]] - h[b[i]]), ++cnt[s[i]];
    if (s[n])return cout << 0 << endl, 0;
    unsigned rs = 1;
    for (auto [_, c] : cnt)rs = add(rs, pw2(c) - 1);
    cout << rs << endl;
    return 0;
}

T3 CF1210G Mateusz and Escape Room

题意

给定环 \(a_{1\sim n}\),一次操作可以选择一对相邻位置,一个加一一个减一(不能 \(<0\)),求使得每个 \(i\) 都有 \(l_i\le a_i\le r_i\) 的最小操作次数,\(0\le n,a_i,l_i,r_i\le35000\)

分析

对于一种操作,令 \(x_i\) 表示 \(i\bmod n+1\) 位置向 \(i\) 位置移动的数量(小于 \(0\) 则反方向移动),显然最小操作次数为 \(\sum |x_i|\)

显然最优情况下 \(|x_i|\le \sum a_i\),假定已经确定 \(x_1\)

\(f_{i,j}\) 表示确定了 \(x_{1\sim i}\) 且任意 \(x_i=j\),且任意 \(1<p\le i\) 都有 \(l_p\le a_p-x_{p-1}+x_p \le r_p\)

转移为

\[f_{i-1,j}\to f_{i,k}+|k|\;\;(l_i\le a_i-j+k\le r_i) \]

初始 \(f_{1,x_1}=0\)\(f_{1,\ast}=\infty\),答案为 \(\min_{l_1\le a_1-j+x_1\le r_1} f_{i,j}\)

转移化简为

\[f_{i-1,j}\to f_{i,k}+|k|\;\;(l_i+j-a_i\le k\le r_i+j-a_i) \]

答案等于 \(\min_{a_1+x_1-r_1\le j\le a_1+x_1-l_1} f_{i,j}\)

转移可以拆分为

  1. \(f_{i-1,j}\to f_{i,r_i+j-a_i}\)(即向右平移 \(r_i-a_i\)
  2. \(f_{i,j}\gets \min_{j\le p\le j+{r_i-l_i}} f_{i,p}\)
  3. \(f_{i,j}\gets f_{i,j}+|j|\)

显然 \(f_i\) 是凸的,用两个堆分别维护 \(k=0\) 的左侧和右侧的拐点的集合,两部分拐点横坐标加法标记,\(k=0\) 部分的纵坐标,以上三个操作和初始状态容易单 \(\log\) 处理,最终的区间查询容易做到 \(n\log\)

一次处理时间复杂度为 \(O(n\log (nV))\)

可证 最值关于 \(x_1\) 是凸的,因此外层三分,时间复杂度 \(O(n\log^2 (nV))\)

代码

比赛结果

\(60+60+80\)\(\text{rk}3\)

posted @ 2025-08-22 20:42  Hstry  阅读(9)  评论(0)    收藏  举报