计数
基本概念
容斥
容斥解决满足多条件的组合方案数问题,将各个条件用集合的形式表达,即可套用容斥原理。
-
:全集。
-
:满足条件 的方案数。
-
:满足任一条件的方案数。
-
:满足全部条件的方案数。
两个公式:
二项式反演
二项式反演一般用于转化恰好和至多(少)之间的数量关系。
常见形式:
高维二项式反演:
-
-
剩下以此类推。
恰好与至多的转化
设 表示至多满足 个条件, 满足恰好 个条件,由二项式反演,得:
恰好与至少的转化
设 为至少满足 个条件, 满足恰好 个条件,由二项式反演,得:
如果题目要求恰好的数量,要么直接求(DP)很好求,要么间接求再用二项式反演求很好求。
错排问题
问题:某人写了 封信和 个信封,如果所有的信都装错了信封。求所有信都装错信封共有多少种不同情况。
证明略。递推式如下:
整除分块
一般解决形如一下的问题:
可以证明,在第一个使 取不同值的 时,
时, 的取值相等。
又可以证明,这样的块不超过 个,于是可以在根号时间复杂度内算出值。
题目选做
数论相关
1. H(n)
整除分块模板题。
#include <cstdio> #include <iostream> using namespace std; typedef long long LL; int T; LL n; int main () { cin >> T; while(T --) { cin >> n; LL ans = 0; for (LL l = 1, r; l <= n; l = r + 1) { r = min(n, n / (n / l)); ans += (n / l) * (r - l + (LL)1); } cout << ans << endl; } return 0; }
2. Calculating
由算数基本定理可知, 就是 的约数个数。将要求的式子转化为:
其中, 等价于 ,可以使用整除分块。
3.[POI2007]ZAP-Queries
求以下式子:
转化为:
正难则反,可以用容斥的思想做,转化为:
即:
莫比乌斯函数即为容斥系数。
因为这个式子就是整除分块的基本式,所以将 做一个前缀和就可以用整除分块。
莫比乌斯函数预处理过程:
void prework() { mul[1] = 1; for (int i = 2; i <= M; i ++) { if (!f[i]) { prime[++tot] = i; mul[i] = -1; } for (int j = 1; j <= tot && i * prime[j] <= M; j ++) { LL k = i * prime[j]; f[k] = 1; if (i % prime[j] == 0) { mul[k] = 0; break; } else mul[k] = mul[i] * -1; } } for (int i = 1; i <= M; i ++) sum[i] = sum[i - 1] + mul[i];//前缀和 }
4. [HAOI2011]Problem b
求下列式子的值:
通过简单容斥,转化为:
类似于矩阵前缀和的形式,可以画出矩阵更加直观的理解。
那么问题就转化成了求
的值,就是上一题
5.四元组统计
正难则反。所有四元组有 个,用容斥减去不合法的方案数,也就是 的方案数,设 表示至少 的四元组数,答案即为:
考虑求出 ,可以统计因数 在数列每个数中是否出现,记 表示因数 出现在数列中数字的个数,那么:
考虑求出 ,暴力一点的想法就是直接枚举每个数的因数,时间复杂度为 ,极限数据恰好是1e8,可以通过此题。
类似于素数筛法,可以利用筛法的思想将每一个数的因数筛出来,这样可以大大提高代码运行效率。
Ps:双倍经验:MSKYCODE - Sky Code
6. NGM2 - Another Game With Numbers
很容易联想到容斥原理,可以用 搜出所有的组合,然后做容斥。
code
#include <cstdio> #include <vector> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long LL; LL n, k, ans; int f[20], b[20]; vector<int> a; inline LL gcd(LL a, LL b) { return b == 0?a:gcd(b, a%b); } inline LL lcm(LL a, LL b) { return a / gcd(a, b) * b; } inline void dfs(int nd, int m, LL s, int p) { if (s > n) return; if (nd == m + 1) { ans += n / s * p; return ; } for (int i = b[nd - 1] + 1; i <= k; i ++) { LL lll = lcm(s, a[i - 1]); b[nd] = i; dfs(nd + 1, m, lll, p); } } int main() { ios::sync_with_stdio(0); cin >> n >> k; for (int i = 1; i <= k; i ++) { int x; cin >> x; a.push_back(x); } int p = 1; for (int i = 0; i < k; i ++) { memset(b, 0, sizeof(b)); dfs(0, i, 1, p); p *= -1; } cout << n - ans << endl; return 0; }
7. Devu and Birthday Celebration
考虑枚举 的因数 ,将 拆分成 份,那么这样拆分出的 必然满足有 这个因数。
然后考虑容斥,在根号时间内枚举 的因数,然后用莫比乌斯函数做容斥系数。即:
8.SQFREE - Square-free integers
直接容斥,求式子:
9. List Of Integers
先来考虑这样一个问题:求 有多少个数与 互质。
问题可以转化为求如下式子的值:
这个式子是我们的老熟人了,直接套容斥。即求如下式子的值:
回到原问题,我们可以二分要求的数,那么 函数就是如上问题。
code
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define R register const int N = 1e6 + 10; const int MAX = 1e7; const int M = 1e6; typedef long long LL; int T; LL x, p, k; int f[N]; LL cnt, ys[N]; LL mul[N], prime[N], tot; inline void init() { mul[1] = 1; for (R int i = 2; i <= M; i ++) { if (!f[i]) { prime[++tot] = i; mul[i] = -1; } for (R int j = 1; j <= tot && i * prime[j] <= M; j ++) { LL t = i * prime[j]; f[t] = 1; if (i % prime[j] == 0) { mul[t] = 0; break; } mul[t] = mul[i] * -1; } } } inline LL check(LL k, LL p) { LL ans = 0; for (R int i = 1; i <= cnt; i ++) { ans += mul[ys[i]] * (k / ys[i]); } return ans; } int main() { ios::sync_with_stdio(0); init(); cin >> T; while (T --) { cin >> x >> p >> k; cnt = 0; for (R int i = 1; i * i <= p; i ++) { if (p % i == 0) { ys[++cnt] = i; if (i * i != p) ys[++cnt] = p / i; } } LL ans_x = check(x, p); LL l = x, r = MAX, ans = 0; while (l <= r) { LL mid = l + r >> 1; if (check(mid, p) - ans_x >= k) ans = mid, r = mid - 1; else l = mid + 1; } cout << ans << endl; } return 0; }
DP相关
1. [JSOI2011]分特产
正难则反。
表示钦定(至少)前 个人没有分到特产,其他的放任自流的方案数。
表示恰好 个人没有分到特产的方案数。
易得, 就是答案,由二项式反演,得:
考虑如何求 数组。
要求 ,只需考虑剩下 个人的取特产情况,考虑将每个特产依次分给这 个人,由于不一定每个人都要分到,所以问题就等价于允许有空盒的不同盒子放相同球的模型,于是得到如下式子:
然后再用上面的式子求出 即为答案。
2.Cheerleaders
正难则反。
表示钦定 条边没有人占,其余放任自流的方案数。
容易得出全集即为
考虑求出 数组,
当 时,除去一条边不能占,那么就有 或者 的区域是爱占不占的,于是:
当 时,除去两条边不能占,那么就有 或 或 的区域是爱占不占的,于是:
同理可得,当 和 的方案数:
套用容斥可知答案为:
3. [JSOI2015]染色问题
正难则反。
咱这有个不需要脑子的二项式反演做法。。。
表示钦定 行, 列一个没染, 个颜色一个没用,其余放任自流的方案数。
表示恰好 行, 列一个没染, 个颜色一个没用的方案数。
易得:
再由高维二项式反演得:
由于答案就是 ,所以:
若直接求这个式子的值,时间复杂度为 ,非常悬。这类柿子一般用二项式定理优化,发现 的指数 与 一点关系没有,于是将 拉到最外面枚举,得:
由二项式定理,得:
时间复杂度降为
4.Sky Full of Stars
正难则反。。。
咱又有一个不需要脑子的高维二项式反演做法。。。
表示钦定 行 列颜色相同,其余的放任自流的方案数。
表示恰好 行 列颜色相同的方案数。
那么 , 。
由二维二项式反演可得:
考虑求出 数组。
有一个显然的性质:如果至少有 行 和 列满足题目要求,那么满足要求的这些行列一定是同种颜色的。如果只有行满足要求,那么行之间就不一定要同种颜色;列也是同理。
于是很快可以求出:
由于 和 都可以在线性时间内求出来,而 不行,所以将 单独拎出来考虑。
注意到 ,所以
code
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long LL; const LL MOD = 998244353; const int N = 1e6 + 10; const int M = 1e6 + 10; LL n, sum1, sum; LL fac[N]; LL ksm(LL a, LL b) { LL sum = 1; while (b) { if (b & 1) sum = sum * a % MOD; a = a * a % MOD; b >>= 1; } return sum; } void init() { fac[0] = 1; for (LL i = 1; i <= N - 10; i ++) fac[i] = fac[i - 1] * i % MOD; } LL C(LL n, LL m) { if (n < m) return 0; LL fz = fac[n], fm = fac[m] * fac[n - m] % MOD; return fz * ksm(fm, MOD - 2) % MOD; } int main() { init(); cin >> n; int p = -1; for (int i = 1; i <= n; i ++) { sum1 += p * C(n, i) * ksm(3, i) % MOD * ksm(3, n * (n - i)) % MOD; sum1 %= MOD; p *= -1; } sum1 = (sum1 * 2 % MOD); p = -1; for (LL i = 1; i <= n; i ++) { sum += p * C(n, i) * (ksm(ksm(3, n - i) - 1, n) - ksm(3, n * (n - i))) % MOD; sum = (sum % MOD + MOD) % MOD; p = p * -1; } sum = (3 * sum + sum1) % MOD; cout << (-sum + MOD) % MOD; return 0; }
PS: 也可以进行二项式反演优化,但是我懒。。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效