[组合记录] 容斥原理
https://renamoe.gitee.io/2021/04/08/容斥原理学习笔记/
[ZJOI2016]小星星
\(f[i][j][S]\) 表示树上点 \(i\),编号成 \(j\),子树内已经有的编号集合为 \(S\)。暴力转移复杂度带个枚举子集。
考虑去掉 \(S\) 的限制,\(S\) 的限制就是 \(S = 2^n - 1\) 其中每个数必须用至多一次。
如果只设 \(f[i][j]\) 的话,就是有可能有编号重复利用。这里就是可以容斥。
钦定一个集合 \(S\),dp 时保证所有编号是 \(S\) 的子集。
那么答案就是 \(ans(|S| = n) - ans(|S| = n - 1) + ans(|S| = n - 2) - \dots\)
容斥系数 \((-1)^{n - |S|}\)。
summary:先写出朴素 dp,考虑限制因素 要求编号不重不漏。对着这个容斥。
[SHOI2016]黑暗前的幻想乡
与上一题类似。考虑限制:\(n - 1\) 个公司各处理一条不同的边。
如果只用一次矩阵树定理,会得到恰好由 \(n - 1\) 个公司组成的方案。
但是,也会多算恰为 \(n - 2\) 个公司组成的方案
于是钦定集合 \(S\),矩阵树定理只加入 \(S\) 中的边。
用 \(ans(|S| = n - 1) - ans(|S| = n - 2) + ans(|S| = n - 3) - \dots\)。
记得当初写的时候,有些人用了 \(determinant\) 的一些性质。
summary:与上题类似。
【UR #5】怎样跑得更快
考虑
显然 i, j 随意,即求
完事后除一除。。。
设 \(f(\gcd(i,j)) = \gcd^k(i,j)\),试图寻找 \(f(n) = \sum_{d|n} f_r(d)\)
那么
然后发现三个莫反完事了。
trick!!!
\(f(n) = \sum_{d|n} g(d)\)
\(f(n) = \sum_{n|d} g(d)\)
已知 \(f\) 求 \(g\)。原理是移项。
点击查看代码
void f_mu(int *f) {
for(int i = 1; i <= n; ++ i)
for(int j = i + i; j <= n; j += i) {
f[j] = (f[j] + P - f[i]);
if(f[j] >= P) f[j] -= P;
}
}
void f_mu_re(int *f) {
for(int i = n; i >= 1; -- i) {
for(int j = i + i; j <= n; j += i) {
f[i] = ( f[i] + P - f[j] );
if(f[i] >= P) f[i] -= P;
}
}
}