容斥原理与Mobius函数

·# 容斥原理
百度百科:容斥原理

S 1 , S 2 , ⋯   , S n S_1, S_2, \cdots ,S_n S1,S2,,Sn为有限集合, ∣ S ∣ |S| S表示集合 S S S的大小,则:
∣ ⋃ i = 1 n S i ∣ = ∑ i = 1 n ∣ S i ∣ − ∑ 1 ≤ i < j ≤ n ∣ S i ∩ S j ∣ + ∑ i ≤ i < j < k ≤ k ∣ S i ∩ S j ∩ S k ∣ + ⋯ + ( − 1 ) n + 1 ∣ S 1 ∩ ⋯ ∩ S n ∣ \left | \bigcup_{i=1}^{n} S_i \right | = \sum_{i=1}^{n}\left | S_i \right | - \sum_{1 \le i < j \le n} \left | S_i\cap S_j \right | + \sum_{i \le i < j < k \le k}\left | S_i \cap S_j \cap S_k \right | + \cdots +(-1)^{n+1}\left | S_1 \cap \cdots \cap S_n \right | i=1nSi=i=1nSi1i<jnSiSj+ii<j<kkSiSjSk++(1)n+1S1Sn

多重集

多重集是指包含重复元素的广义集合。设 S = { n 1 ∗ a 1 , n 2 ∗ a 2 , ⋯   , n k ∗ a k } S = \left \{ n_1 * a_1 , n_2 * a_2, \cdots , n_k * a_k \right \} S={n1a1,n2a2,,nkak},是由 n 1 n_1 n1 a 1 a_1 a1 n 2 n_2 n2 a 2 a_2 a2 ⋯ \cdots n k n_k nk a k a_k ak组成的多重集。则 S S S 的全排列个数为:
n ! n 1 ! n 2 ! ⋯ n k ! \frac{n !}{n_1 !n_2! \cdots n_k !} n1!n2!nk!n!
r ≤ n i ( ∀ ∈ [ 1 , k ] r \le n_i ( \forall \in[1, k] rni([1,k]。从 S S S中取出 r r r个元素组成一个多重集(不考虑顺序),产生的不同多重集数量为:
C k + r − 1 k − 1 C_{k+r - 1}^{k - 1} Ck+r1k1
n = ∑ i = 1 k n i n = \sum_{i=1}^{k} n_i n=i=1kni,对于任意整数: r ≤ n r \le n rn,从 S S S中取出 r r r个元素组成的一个多重集(不考虑顺序),产生的不同多重集的数量为:
C k + r − 1 k − 1 − ∑ i = 1 k C k + r − n i − 2 k − 1 + ∑ 1 ≤ i < j ≤ k C k + r − n i − n j − 3 k − 1 − ⋯ + ( − 1 ) k C k + r − ∑ i = 1 k n i − ( k + 1 ) C_{k+r - 1}^{k-1} - \sum_{i=1}^{k}C_{k + r -n_i - 2}^{k - 1} + \sum_{1 \le i < j \le k} C_{k + r -n_i - n_j - 3}^{k - 1} - \cdots + (-1)^{k} C_{k + r -\sum_{i=1}^{k}n_i - (k + 1)} Ck+r1k1i=1kCk+rni2k1+1i<jkCk+rninj3k1+(1)kCk+ri=1kni(k+1)

例题1:

题目链接

题意:

Devu 有 N N N 个盒子,第 i i i 个盒子中有 A i A_i Ai 枝花。
同一个盒子内的花颜色相同,不同盒子内的花颜色不同。
Devu 要从这些盒子中选出 M M M 枝花组成一束,求共有多少种方案。
若两束花每种颜色的花的数量都相同,则认为这两束花是相同的方案。
结果需对 1 0 9 + 7 10^9+7 109+7 取模之后方可输出。

Sol:

根据多重集组合数结论,本体答案为上式: C k + r − 1 k − 1 − ∑ i = 1 k C k + r − n i − 2 k − 1 + ∑ 1 ≤ i < j ≤ k C k + r − n i − n j − 3 k − 1 − ⋯ + ( − 1 ) k C k + r − ∑ i = 1 k n i − ( k + 1 ) C_{k+r - 1}^{k-1} - \sum_{i=1}^{k}C_{k + r -n_i - 2}^{k - 1} + \sum_{1 \le i < j \le k} C_{k + r -n_i - n_j - 3}^{k - 1} - \cdots + (-1)^{k} C_{k + r -\sum_{i=1}^{k}n_i - (k + 1)} Ck+r1k1i=1kCk+rni2k1+1i<jkCk+rninj3k1+(1)kCk+ri=1kni(k+1)

  • tips: 二进制表示集合,利用 l u c a s lucas lucas 求组合数
Code:
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 22, mod = 1e9 + 7;
int a[22], inv[22];
int n ,m;

int qpow(int a, int b)
{
    int res = 1;
    while(b) {
        if( b & 1) res = (long long)res * a % mod;
        a = (long long)a * a % mod;
        b >>= 1;
    }
    return res;
}

int Inv(int x)
{
    return qpow(x, mod - 2);
}

int C(int a, int b)
{
    if(a < b) return 0;
    int res = 1;
    for(int i = 1, j = a; i <= b; ++i, -- j)
    {
        res = res * j % mod;
        res = res * inv[i] % mod;
    }
    return res;
}

int lucas(int a, int b, int p)
{
    if(a < p && b < p) return C(a, b);
    return C(a % p, b % p) * lucas(a / p, b / p, p);
}

signed main()
{
    for(int i = 1; i <= 20; ++i) inv[i] = Inv(i);
    // 预处理所有逆元(直接算超时)
    cin >> n >> m;
    int ans = 0;
    for(int i = 1; i <= n; ++i) cin >> a[i];
    for(int i = 0; i < 1 << n; ++i)
    {
        if(i == 0)
        {
            ans = (ans + lucas(n + m - 1, n - 1, mod)) % mod;
        }
        else 
        {
            long long t = n + m;
            int p = 0;
            for(int j = 0; j < n; ++j)
            {
                if(i >> j & 1 )
                {
                    t -= a[j + 1];
                    p ++;
                }
            }
            t -= p + 1;
            if(p & 1) ans = (ans - lucas(t, n - 1, mod)% mod + mod) % mod;
            else ans = (ans + lucas(t, n - 1, mod)) % mod;
        }
    }
    cout << (ans % mod + mod) % mod << endl;
}

Mobius函数

设正整数 N N N按照算术基本定理分解质因数可以写成: N = p 1 c 1 p 2 c 2 ⋯ p m c m N = p_1^{c_1}p_2^{c_2} \cdots p_m^{c_m} N=p1c1p2c2pmcm,定义函数
μ ( x ) = { 0 ∃ i ∈ [ 1 , m ] , c i > 1 1 m ≡ 0 (   m o d   2 ) , ∀ i ∈ [ 1 , m ] , c i = 1 − 1 m ≡ 1 (   m o d   2 ) , ∀ i ∈ [ 1 , m ] , c i = 1 μ(x) = \begin{cases} 0 \quad \exists i \in[1, m],c_i > 1 \\ 1 \quad m \equiv0(\bmod 2), \forall i \in [1, m], c_i = 1 \\ -1 \quad m \equiv 1(\bmod 2), \forall i \in [1, m], c_i = 1 \end{cases} μ(x)=0i[1,m]ci>11m0(mod2),i[1,m],ci=11m1(mod2),i[1,m],ci=1
求某一项 M o b i u s Mobius Mobius的值,可以分解质因数计算.
若要求 1 ∼ n 1 \sim n 1n的每一项 M o b i u s Mobius Mobius的值。我们依旧可以利用埃式筛,或者线性筛来计算,时间复杂度接近 O ( n ) O(n) O(n)
代码

// 埃筛
void Mobius(int n)
{
    for(int i = 1; i <= n; ++i) mobius[i] = 1, st[i] = 0;
    for(int i = 2; i <= n; ++i)
    {
        if(!st[i])
        {
            mobius[i] = -1;
            for(int j = i + 1; j <= n; j += i)
            {
                st[j] = 1;
                if((j / i) % i == 0) mobius[j] = 0;
                mobius[j] *= -1;
            }
        }
    }
}
// 线性筛
void Mobius(int n)
{
	mobius[1] = 1;
    for(int i = 2; i <= n; ++ i)
    {
        if(!st[i])
        {
            prime[++ idx] = i;
            mobius[i] = -1;
        }
        for(int j = 1; i * prime[j] <= n; ++j)
        {
            st[i * prime[j]] = true;
            if(i % prime[j] == 0) {
                mobius[i * prime[j]] = 0;
                break;
            }
            mobius[i * prime[j]] = - mobius[i];
        }
    }
}

例题2:

题目链接

题意:

达达正在破解一段密码,他需要回答很多类似的问题:
对于给定的整数 a , b a,b a,b d d d,有多少正整数对 x , y x,y x,y,满足 x ≤ a , y ≤ b x \le a,y \le b xayb,并且 g c d ( x , y ) = d gcd(x,y)=d gcd(x,y)=d
作为达达的同学,达达希望得到你的帮助。

Sol:

首先: g c d ( a , b ) = d ⇔ g c d ( a / d , b / d ) = 1 gcd(a, b) = d \Leftrightarrow gcd(a / d, b / d) = 1 gcd(a,b)=dgcd(a/d,b/d)=1
证明很简单: x , y x,y x,y的最大公因数是 d d d那么他们分别除了 d d d以后一定是互质的。很显然
那么问题转化为:求 x ≤ a / d , y ≤ b / d x \le a/d,y \le b/d xa/dyb/d满足 g c d ( x , y ) = 1 gcd(x, y) = 1 gcd(x,y)=1的个数。我们考虑容斥
则答案为:所有的对数 - 不互质的对数
用式子表示:
∑ i = 1 m i n ( a , b ) ⌊ a i ⌋ ∗ ⌊ b i ⌋ ∗ μ ( i ) \sum_{i = 1}^{min(a, b)} \left \lfloor \frac{a}{i} \right \rfloor*\left \lfloor \frac{b}{i} \right \rfloor * μ(i) i=1min(a,b)iaibμ(i)
证明:
令: a = ⌊ a d ⌋ , b = ⌊ b d ⌋ a = \left \lfloor \frac{a}{d} \right \rfloor, b = \left \lfloor \frac{b}{d} \right \rfloor a=da,b=db, n = m i n ( a , b ) n = min(a, b) n=min(a,b),那么所有的对数: a ∗ b a * b ab

接下来要找出 g c d ( x , y ) > 1 gcd(x, y) > 1 gcd(x,y)>1的数量。
不妨设集合 S i , T i S_i, T_i Si,Ti,分别表示 [ 1 , a ] [1, a] [1,a]中因子有 i i i的数的个数 ⌊ a i ⌋ \left \lfloor \frac{a}{i} \right \rfloor ia [ 1 , b ] [1, b] [1,b]中因子有 i i i的数的个数 ⌊ b i ⌋ \left \lfloor \frac{b}{i} \right \rfloor ib。那么容易知道,两个数不互质,那么他们的公因子大于等于 2 2 2
根据容斥定理和 M o b i u s Mobius Mobius函数的定义,则有不互素的个数为:
∑ i = 2 n ⌊ a i ⌋ ∗ ⌊ b i ⌋ ∗ μ ( i ) \sum_{i=2}^{n} \left \lfloor \frac{a}{i} \right \rfloor*\left \lfloor \frac{b}{i} \right \rfloor * μ(i) i=2niaibμ(i)

此时,时间复杂度以及是 O ( n ) O(n) O(n),但此题仍然会超时。考虑分块做法,时间复杂度 O ( n ) O(\sqrt n) O(n )

Code:
#include <bits/stdc++.h>
using namespace std;
const int N = 50010;

int prime[N], mobius[N], sum[N], idx;
int n, a, b, d;
bool st[N];

void Mobius(int n)
{
    for(int i = 1; i <= n; ++i) mobius[i] = 1, st[i] = 0;
    for(int i = 2; i <= n; ++i)
    {
        if(!st[i])
        {
            mobius[i] = -1;
            for(int j = i + 1; j <= n; j += i)
            {
                st[j] = 1;
                if((j / i) % i == 0) mobius[j] = 0;
                mobius[j] *= -1;
            }
        }
    }
}

void init( int n )
{
    mobius[1] = 1;
    for(int i = 2; i <= n; ++ i)
    {
        if(!st[i])
        {
            prime[++ idx] = i;
            mobius[i] = -1;
        }
        for(int j = 1; i * prime[j] <= n; ++j)
        {
            st[i * prime[j]] = true;
            if(i % prime[j] == 0) {
                mobius[i * prime[j]] = 0;
                break;
            }
            mobius[i * prime[j]] = - mobius[i];
        }
    }
    for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + mobius[i];
}

int main()
{
    init(N - 1);
    int tt;
    scanf("%d", &tt);
    while(tt -- )
    {
        scanf("%d%d%d", &a, &b, &d);
        a /= d, b /= d;
        n = min(a, b);
        long long ans = 0;
        for(int l = 1, r; l <= n; l = r + 1)
        {
            r = min(n, min(a / (a / l), b / (b / l)));
            ans += (sum[r] - sum[l - 1]) *(long long)(a / l) * (b / l);
        }
        printf("%lld\n", ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

W⁡angduoyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值