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_{1,x_1}=0\),\(f_{1,\ast}=\infty\),答案为 \(\min_{l_1\le a_1-j+x_1\le r_1} f_{i,j}\)
转移化简为
答案等于 \(\min_{a_1+x_1-r_1\le j\le a_1+x_1-l_1} f_{i,j}\)
转移可以拆分为
- \(f_{i-1,j}\to f_{i,r_i+j-a_i}\)(即向右平移 \(r_i-a_i\))
- \(f_{i,j}\gets \min_{j\le p\le j+{r_i-l_i}} f_{i,p}\)
- \(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\)

浙公网安备 33010602011771号