前置知识-容斥定理:
将 事 件 A ,表 示 成 多 个 子 事 件 A 1 , A 2 ...A k 的 并 集 ,不 要 求 事 件 互 斥 ,即 ⋃A i = A
容 斥 定 理 ,可 以 让 我 们 用 这 些 子 事 件 的 交 集 计 算 整 个 事 件
1. 两集合的简单容斥
2. 更一般的容斥定理
3. 子集容斥-子集反演
为 某 集 合 , 若 两 个 函 数 满 足 如 下 关 系 : 则 其 逆 运 算 为 :
题意
长 度 为 n 的 序 列 a , gcd (a ) = x , l c m (a ) = y , 求 序 列 方 案 数
解析
对 于 x ,y ,由 唯 一 分 解 定 理 ,我 们 可 拆 解 成 如 下 形 式
不同质因子相互独立,考虑研究某个质因子,假设是 p 1
分解 a i = p 1 k i 1p 2 k i 2p 3 k i 3 ...
因为gcd (a ) = x ,我们有 min k i 1 = c 1
因为l c m (a ) = x ,我们有
max k i 1 = e 1
也就是所有序列中所有数,都要满足,质 因 子 的 幂 ,且至少存在一个数的幂是 c 1 ,至少存在一个数的幂是
c 2
设 t = y /x ,分解
t = p 1 k 1 p 2 k 2 p 3 k 3 ...
对于某种质因子,我们等价转化成如下问题:
有 n 个盒子,每个盒子可以放 [0, k] 个球,至少有一个盒子放了 0
个,且至少有一个盒子放了 k 个,求方案数
正向算不好算,考虑无 0,无 k
定 义 : 没 有 盒 子 放 了 个 的 情 况 集 合 , 没 有 盒 子 放 了 个 的 情 况 集 合
定 义 : 至 少 有 一 个 盒 子 放 了 个 , 且 至 少 有 一 个 盒 子 放 了 个 的 情 况 集 合
易得,A = U − A 1 ∪ A 2
由容斥定理
MYCODE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> using namespace std;using ll = long long ;const int mo = 998244353 ;ll qpow (ll a, ll x) { ll ans = 1 ; while (x){ if (x & 1 ) ans *= a; a *= a; x >>= 1 ; a %= mo; ans %= mo; } return ans; } int main () { int q; cin >> q; for (int qq=0 ; qq<q; qq++){ int x, y, n; cin >> x >> y >> n; int z = y/x; map<int , int > fct; for (int i=2 ; i*i<=z; i++){ while (z % i == 0 ){ z /= i; fct[i]++; } } if (z != 1 ) fct[z]++; ll ans = 1 ; for (auto [p, k] : fct){ ans *= (qpow (k+1 , n) - 2 *qpow (k, n) + qpow (k-1 , n)) % mo; ans = (ans%mo + mo) % mo; } cout << ans << '\n' ; } }
题意
n ⋅ m 个 格 子 , 每 个 格 子 都 可 以 放 或 不 放 宝 石 ,且 每 行 每 列 都 至 少 有 一 个 宝 石 ,求 方 案 数
(n , m ≤ 5e 4)
解析
正向算不好算,考虑存在空行或空列,设为情况集合 S,a n s = 2n m − |S |
容斥定义 :
定 义 :r i 为 第 i 行 为 空 的 情 况 集 合 ,c i 为 第 i 列 为 空 的 情 况 集 合
定 义 :R i 为 至 少 有 i 行 为 空 的 情 况 集 合 ,C i 为 至 少 有 i 列 为 空 的 情 况 集 合
或
发现 i,j 可以分开,简化求和。
由于没有 i + j = 0 这一项,即没有 i = j = 0
的情况,这种情况我们要减去
易 得 , 代 入
O (n 2 )复 杂 度 ,还 需 要 继 续 优 化 ,可 将 i 提 出
回忆一下二项式定理 →
你可能已经注意到了,
不正是二项式定理的形式么
我们让它更规范一些 →
所以我们有
终于,到现在 |S| 就可以 o(nlog) 计算了
递推预处理一下 ,这样,这道题就完美解决了
MYCODE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <bits/stdc++.h> using namespace std;using ll = long long ; const int mo = 1e9 + 7 ; const int MX = 2e5 ;vector<ll> fct (MX+1 ) ;vector<ll> inv_fct (MX+1 ) ;vector<ll> inv (MX+1 ) ; void init () { fct[0 ] = inv_fct[0 ] = inv[1 ] = 1 ; for (int i=2 ; i<=MX; i++){ inv[i] = (mo - mo / i) * inv[mo % i] % mo; } for (int i=1 ; i<=MX; i++){ fct[i] = fct[i-1 ] * i % mo; inv_fct[i] =inv_fct[i-1 ] * inv[i] % mo; } } ll comb (ll n, ll k) { ll ans = 1ll * fct[n] * inv_fct[n-k] % mo * inv_fct[k] % mo; return ans; } ll qpow (ll a, ll x) { ll ans = 1 ; while (x){ if (x & 1 ) ans *= a; a *= a; x >>= 1 ; a %= mo; ans %= mo; } return ans; } void solve () { int n, m; cin >> n >> m; ll ans = 0 ; for (int i=0 ; i<=n; i++){ ans += (i&1 ? -1 :1 ) * comb (n, i) * qpow ((qpow (2 , n-i) - 1 ), m) % mo; ans = (ans + mo) % mo; } cout << ans << '\n' ; } int main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); init (); int t = 1 ; cin >> t; while (t--) solve (); return 0 ; }
题意
y ∈ [1, Y ],有 n 种 事 件 A 1 …A n ,每 种 事 件 仅 在 a i ∣ y 时 发 生 ,求 有 多 少 个 y ,能 使 恰 好 m 个 事 件 发 生
( m ≤ n ≤ 20, Y ≤ 1e 18, a i ≤ 1e 18 )
解析
子集容斥(子集莫比乌斯反演):
事件集合 S
设 g (S ) 为 至 少 覆 盖 S 的 情 况 数 (S 中 的 事 件 全 发 生 ,其 他 事 件 可 以 发 生 或 不 发 生 )
设 f (S ) 为 恰 好 S 的 情 况 数 (仅 S 中 事 件 发 生 ,其 他 事 件 不 发 生 )
显 然 , 为 中 所 有 事 件 的 的
我们有 g (S ) = ∑S ⊆ T ⊆ U f (T )
由子集反演公式:
我们需要计算 满足|S|=m 的所有子集 S 的答案,即
又到了精彩的推式子环节
对于每个子集去枚举超集,时间复杂度 o (3n ) ,需要优化
可以交换 S, T 求和顺序
这 样 就 优 化 完 了
枚 举 m ≤ |T | 的 所 有 子 集 ,O (2n )
预 处 理 g (T ),O (2n l o g )
预 处 理 ,
总 时 间 复 杂 度 O (2n l o g + n )
MYCODE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <bits/stdc++.h> using namespace std;using ll = long long ;using ull = unsigned long long ;using i128 = __int128;const ll inf = 2e18 ;const int MX = 20 ;vector<ll> fct (MX + 1 ) ;void init () { fct[0 ] = 1 ; for (int i=1 ; i<=MX; i++){ fct[i] = i * fct[i-1 ]; } } void solve () { ll n, m, Y; cin >> n >> m >> Y; vector<ll> a (n) ; for (int i=0 ; i<n; i++) cin >> a[i]; auto get = [&](ll x){ int p = __lg(x & -x); return p; }; auto comb = [&](ll n, ll k){ return fct[n] / fct[n-m] / fct[m]; }; vector<ll> pre (1 << n, 1 ) , g (1 << n) ; int full = (1 << n) - 1 ; for (int S = 1 ; S <= full; S++){ int lo = get (S); i128 l = i128 (pre[S ^ (1 << lo)]) * a[lo] / gcd (pre[S ^ (1 << lo)], a[lo]); if (l > Y){ pre[S] = inf; continue ; } pre[S] = l; g[S] = Y / pre[S]; } ull ans = 0 ; for (int S = 1 ; S <= full; S++){ int k = __builtin_popcount(S); if (k < m) continue ; ans += 1ull * comb (k, m) * g[S] * ((k-m)&1 ? -1 :1 ); } cout << ans << '\n' ; } int main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); init (); int t = 1 ; while (t--) solve (); return 0 ; }