容斥原理

Preface#

可能容斥原理的公式等还是 AK IOI 的巨佬讲得详细,大家可以看看这篇博客

这篇博客把我写得手残疾了。

我这里直接上公式:

|i=1NSi|=i=1N|Si|i=1Nj=i+1N|SiSj|+i=1Nj=i+1Nk=j+1N|SiSjSk|

【例题 1#

Devu and Flowers

不要看这个难度就吓跑了,这题综合性比较强,但每个知识点我相信大家都会的,只不过综合起来就变成这个难度了。

先在这里明确一下(与题目翻译有所不同)n 表示花瓶的个数,m 表示需要的花的数量,fi 表示第 i 个花瓶花的朵数。

【题目大意】#

让我们求出序列 q 的个数。

其中序列 q 要满足以下条件:

  1. (i=1nqi)=m
  2. qifi

【分析】#

如果我们去除条件 2,则让我们求 (i=1nqi)=m 的非负整数解的个数。

首先我们把 yi=qi+1,则我们要求出 (i=1nyi)=n+m 的正整数解的个数。

即求不定方程正整数解的个数,这个可以用隔板法做。

形象化的说:

1 11 1 1n+m 个 1 中放入 n1 个隔板(至于为什么是 n1 个隔板,我都不知道该怎么讲,可以滚回小学去了)。

那么我们要从 n+m1 个空隙中(至于为什么是 n+m1 个空隙,我依旧不知道怎么讲)选出 n1 个空隙放隔板,那么这个就是小学奥数题,即为 (n+m1n1)(这玩意就等于 Cn+m1n1)。

然后加上条件 2,对于一个不合法的解至少有一个 1in 满足 qi>fi

我们把 qi>fiqi 表示成一个集合 Si

则不合法的解为一开始的公式(我只不过复制了一下):

|i=1NSi|=i=1N|Si|i=1Nj=i+1N|SiSj|+i=1Nj=i+1Nk=j+1N|SiSjSk|

则答案就为:

(n+m1n1)|i=1NSi|=(n+m1n1)i=1N|Si|+i=1Nj=i+1N|SiSj|i=1Nj=i+1Nk=j+1N|SiSjSk|

然后问题又来了,如果求上面的答案呢。

我们发现如果 Si 不合法必定是 qi>fi 这个就为 qifi+1,那么我们把 qi 减去 fi+1

那么不管你剩余的 m(fi+1) 个花怎么选都是 Si 的。

这里是没有 fi 进行限制,那么这用回到了之前求不定方程解的个数。

答案就为 (n+mfi2n1)(这玩意就为 Cn+mfi2n1),如果看到这里模糊的话请仔细阅读起那面求不定方程的过程。

那么如果是 |SiSj| 也是同理。

要同时满足 qi>fiqj>fj,那么和上面一样减去 qi+1qj+1

然后再从剩余的 m(qi+1)(qj+1) 朵花中去选。

答案为 (n+mfifj3n1)(这玩意就为 Cn+mfifj3n1)。

然后我们把答案再写一下:

(n+m1n1)|i=1NSi|=(n+m1n1)i=1N(n+mfi2n1)+i=1Nj=i+1N(n+mfifj3n1)i=1Nj=i+1Nk=j+1N(n+mfifjfk4n1)

这个组合数的求法就按照 Cba=b×(b1)××(ba+1)a!,因为 a20,与 n 同级,所以时间复杂度为 O(n)

当然这个要用逆元即 abab1(modp)b1b 的逆元即为 bp2p 为模数(证明可以去百度搜费马小定理)。

然后那个柿子可以用二进制枚举来求,如果二进制中第 i 位是 1 表示要属于集合 Si

时间复杂度:O(2n×n),空间复杂度:O(n)

Code#

#include <bits/stdc++.h>

#define x first
#define y second
#define IOS ios::sync_with_stdio(false)
#define cit cin.tie(0)
#define cot cout.tie(0)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;

const int N = 20, M = 100010, MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const LL LLINF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-8;

void solve();

int main()
{
    IOS;
    cit, cot;
    int T = 1;
    // cin >> T;
    while (T -- ) solve();
    return 0;
}

LL n, m;
LL q[N];

int qmi(int a, int k)
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % MOD;
        a = (LL)a * a % MOD;
        k >>= 1;
    }

    return res;
}

int C(LL a, LL b)
{
    if (a < b) return 0;
    int x = 1, y = 1;
    for (LL i = a - b + 1; i <= a; i ++ ) x = i % MOD * x % MOD;
    for (int i = 1; i <= b; i ++ ) y = y * (LL)i % MOD;
    return x * (LL)qmi(y, MOD - 2) % MOD;
}

void solve()
{
    cin >> n >> m;
    for (int i = 0; i < n; i ++ ) cin >> q[i];

    int res = 0;
    for (int i = 0; i < 1 << n; i ++ )
    {
        LL a = m + n - 1, b = n - 1;
        int sign = 1;
        for (int j = 0; j < n; j ++ )
            if (i >> j & 1)
            {
                sign *= -1;
                a -= q[j] + 1;
            }
        res = ((res + C(a, b) * sign) % MOD + MOD) % MOD;
    }
    cout << res << endl;
}

【例题 2#

硬币购物

这题我就不写得像前一题那么详细。

先来考虑如果没有 di 限制,di 的含义见题意。

那么就是一道完全背包的模板题。

如果加上 di 限制,我们将 qi>diqi 加入集合 Siqi 为第 i 张纸牌拿的张数)。

fi 表示用这些无穷无尽钱凑出价值为 i 的方案数。

那么答案为:

fm|i=1NSi|=fmi=1N|Si|+i=1Nj=i+1N|SiSj|i=1Nj=i+1Nk=j+1N|SiSjSk|

|Si| 我们也用上题的思路去求,将 di+1 张钱拿去,那么 m 应该减去 (di+1)×cim 为价值,ci 为钱的面值。

然后问题转换成了求没有 di 限制,且价值为 m(di+1)×ci 的方案数,这也是完全背包的裸题。

|SiSj| 等不展开了,具体见柿子。

我们再写一遍答案:

fm|i=1NSi|=fmi=1Nfm(di+1)×ci+i=1Nj=i+1Nfm(di+1)×ci(dj+1)×cji=1Nj=i+1Nk=j+1Nfm(di+1)×ci(dj+1)×cj(dk+1)×ck

Code#

#include <bits/stdc++.h>

#define x first
#define y second
#define IOS ios::sync_with_stdio(false)
#define cit cin.tie(0)
#define cot cout.tie(0)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;

const int N = 100010, M = 100010, MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const LL LLINF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-8;

void solve();

int main()
{
    IOS;
    cit, cot;
    int T = 1;
    // cin >> T;
    while (T -- ) solve();
    return 0;
}

int c[4], n;
LL f[N];

void solve()
{
    cin >> c[0] >> c[1] >> c[2] >> c[3] >> n;
    f[0] = 1;
    for (int i = 0; i < 4; i ++ )
        for (int j = c[i]; j < N; j ++ )
            f[j] += f[j - c[i]];
    while (n -- )
    {
        int d[4], _s;
        cin >> d[0] >> d[1] >> d[2] >> d[3] >> _s;

        LL res = 0;
        for (int i = 0; i < 1 << 4; i ++ )
        {
            int sign = 1;
            LL s = _s;
            for (int j = 0; j < 4; j ++ )
                if (i >> j & 1)
                {
                    sign *= -1;
                    s -= c[j] * (d[j] + 1ll);
                }
            if (s >= 0) res += sign * f[s];
        }
        cout << res << endl;
    }
}

【例题 3#

Count GCD

Solution#

先进行转化,gcd(b1,b2,,bi)=gcd(gcd(b1,b2,,bi1),bi)=gcd(ai1,bi)=ai

也就是让 gcd(ai1ai,biai)=1

再进行转化,就是让 gcd(ai1ai,bi)=1 并且 bimai

我们先不考虑条件 1,那么答案为 mai

然后我们再加上条件 1,那么我们将 n=ai1ai,则我们将 n 分解质因式后为 n=p1α1p2α2pqαq

然后根据容斥原理,即减去所有的 gcd(ai1ai,bi)>1

然后我们再书写一遍答案:

maii=1qSpi+i=1qj=i+1qSpipji=1qj=i+1qk=j+1qSpipjpk+

其中 Si 表示 maii,即 [1,mai] 中所有 i 的倍数的个数。

因为 2×3×5×7×11×13×17×19×23109,所以 q9

当然这里要加点小优化,即如果 aiai1 时,那么直接输出 0,不用做下面计算,否则你会喜提 TLE

因为满足 aiai1 的序列,一定满足 a 是单调递增的,那么 i=1n1ai+1ai 不会被卡满,大约是 O(mlogm)

Code#

#include <bits/stdc++.h>

#define x first
#define y second
#define IOS ios::sync_with_stdio(false)
#define cit cin.tie(0)
#define cot cout.tie(0)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;

const int N = 200010, M = 100010, MOD =  998244353;
const int INF = 0x3f3f3f3f;
const LL LLINF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-8;

int n, m;
int a[N];

void solve();

int main()
{
    IOS;
    cit, cot;
    int T = 1;
    cin >> T;
    while (T -- ) solve();
    return 0;
}

void solve()
{
    cin >> n >> m;
    bool flag = true;
    for (int i = 1; i <= n; i ++ )
    {
        cin >> a[i];
        if (i > 1 && a[i - 1] % a[i])
        {
            flag = false;
        }
    }
    if (!flag)
    {
        cout << 0 << endl;
        return;
    }

    int res = 1;
    for (int i = 2; i <= n; i ++ )
    {
        int k = m / a[i], t = a[i - 1] / a[i];

        vector<int> p;
        for (int j = 2; j <= t / j; j ++ )
            if (t % j == 0)
            {
                p.push_back(j);
                while (t % j == 0) t /= j;
            }
        if (t > 1) p.push_back(t);

        int sum = 0, sz = p.size();
        for (int i = 0; i < 1 << sz; i ++ )
        {
            int sign = 1, s = 1;
            for (int j = 0; j < sz; j ++ )
                if (i >> j & 1)
                {
                    sign *= -1;
                    s *= p[j];
                }
            sum += sign * (k / s);
        }

        res = res * (LL)sum % MOD;
    }
    cout << res << endl;
}
posted @   hcywoi  阅读(65)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示
主题色彩