容斥与简单反演乱写
#define TBD ToBeDone 😅
容斥的本质
构造一组数,使得 ,其中为希望这个元素被统计的次数,为容斥系数,为超集的大小。
可以理解对于一个需要统计的元素为枚举包含其集合的大小,对于所有大小为的集合将其中元素都统计次,使得最后被恰好统计次。
对于常规的容斥题,有,此时,可以带入用二项式定理验证。
例题
sol
点击查看代码
#include <bits/stdc++.h> #define int long long const int MAXN = 65; struct Edge { int to, nxt; }E[MAXN << 1]; int head[MAXN], ecnt = -1; void add(int u, int v) { E[++ecnt].to = v; E[ecnt].nxt = head[u]; head[u] = ecnt; } void solve() { memset(head, -1, sizeof head); int n, m, k; std::cin >> n >> m >> k; struct Dsu { std::vector <int> fa; void init(int n) { fa.resize(n + 1); for (int i = 0; i <= n; i++) fa[i] = i; } int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); } void merge(int x, int y) { int X = find(x), Y = find(y); if (X == Y) return; fa[X] = Y; } }dsu; for (int i = 1; i < n; i++) { int u, v; std::cin >> u >> v; add(u, v); add(v, u); } std::vector <std::vector<int> > path(m); std::vector <int> ine(n + 1); auto dfs = [&](auto self, int u, int fa) -> void { for (int i = head[u]; i != -1; i = E[i].nxt) { int v = E[i].to; if (v == fa) continue; ine[v] = i ^ 1; self(self, v, u); } }; for (int i = 0; i < m; i++) { int u, v; std::cin >> u >> v; dfs(dfs, u, -1); int now = v; while (now != u) { path[i].push_back(ine[now] / 2); now = E[ine[now]].to; } } int up = (1 << m) - 1; int ans = 0; for (int mask = 0; mask <= up; mask++) { dsu.init(n - 1); for (int i = 0; i < m; i++) { if (mask & (1 << i)) { for (const auto &x : path[i]) { if (x == path[i][0]) continue; dsu.merge(path[i][0], x); } } } int cnt = 0; for (int i = 0; i < n - 1; i++) { if (dsu.find(i) == i) cnt++; } const int MOD = 1e9 + 7; auto power = [&](int a, int b) { int ret = 1; for (; b; b >>= 1) { if (b & 1) ret = (1ll * ret * a) % MOD; a = (1ll * a * a) % MOD; } return ret; }; int contr = power(k, cnt); if (__builtin_popcount(mask) & 1) ans -= contr, ans += MOD, ans %= MOD; else ans += contr, ans %= MOD; } std::cout << ans << "\n"; } signed main() { freopen("decoration.in", "r", stdin); freopen("decoration.out", "w", stdout); std::ios::sync_with_stdio(0); std::cin.tie(0); std::cout.tie(0); int t = 1; while (t--) { solve(); } return 0; }
sol
三个月前做过,现在不会了,不想某位神仙两年前做过现在一眼秒了😅比较navie的想法是设为的子树中有个点未被覆盖,需要子树外一个点向内某个点相连的方案数,直接类似树背包转移有 。
系数过于复杂,不便于优化。看到每条边至少被覆盖一次考虑容斥。
考虑边集中的边一次都没被覆盖过,这等价于删去这些边。此时整张图被分成了若干个联通块,显然每个联通块内部匹配的方案数是 ,注意为奇数时。
数据范围无法枚举子集,考虑,重定义为以为根的子树联通块大小为的方案数(考虑容斥系数)。那么有这条边断掉 (断边集合大小加一),保留这条边 。
最后答案即为 。复杂度为树形背包的 。
点击查看代码
#include <bits/stdc++.h> void solve() { int n; std::cin >> n; std::vector <std::vector<int> > adj(n + 1); for (int i = 1; i < n; i++) { int u, v; std::cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); } const int MOD = 1e9 + 7; std::vector <int> g(n + 1); g[0] = 1; for (int i = 2; i <= n; i += 2) g[i] = 1ll * g[i - 2] * (i - 1) % MOD; std::vector <std::vector<int> > dp(n + 1); std::vector <int> siz(n + 1); for (int i = 0; i <= n; i++) dp[i].resize(n + 1); auto dfs = [&](auto self, int u, int fa) -> void { siz[u] = 1; dp[u][1] = 1; for (const auto &v : adj[u]) { if (v == fa) continue; self(self, v, u); std::vector <int> tmp(siz[u] + siz[v] + 1); for (int j = 1; j <= siz[u]; j++) { for (int k = 1; k <= siz[v]; k++) { tmp[j] += -1ll * dp[u][j] * dp[v][k] % MOD * g[k] % MOD; tmp[j] %= MOD; tmp[j] += MOD; tmp[j] %= MOD; tmp[j + k] += 1ll * dp[u][j] * dp[v][k] % MOD; tmp[j + k] %= MOD; } } siz[u] += siz[v]; for (int i = 1; i <= siz[u]; i++) dp[u][i] = tmp[i]; } }; dfs(dfs, 1, 0); int ans = 0; for (int i = 1; i <= n; i++) { ans += 1ll * dp[1][i] * g[i] % MOD; ans %= MOD; } std::cout << ans << "\n"; } int main() { std::ios::sync_with_stdio(0); std::cin.tie(0); std::cout.tie(0); int t = 1; while (t--) { solve(); } return 0; }
子集反演
若,那么有 。(子集形式)
若,那么有 。(超集形式)
例题
TBD
二项式反演
若,那么有。
若,那么有。
证明:咕咕咕。
发现两种形式左右都是对称的,且-1的次数都为,便于背诵。
本质是容斥在大小相同的集合贡献相同的一种特殊形式。
一般设为恰好取个的值,为钦定取个的值,通常情况下后者比前者更好计算,就完成了从至少到恰好的转化
例题
sol:TBD
点击查看代码
#include <bits/stdc++.h> void solve() { int n, k; std::cin >> n >> k; const int MOD = 1e9 + 7; std::vector <int> fac(n + 1), inv(n + 1); auto power = [&](int a, int b) { int ret = 1; for (; b; b >>= 1) { if (b & 1) ret = (1ll * ret * a) % MOD; a = (1ll * a * a) % MOD; } return ret; }; fac[0] = 1; for (int i = 1; i <= n; i++) fac[i] = (1ll * fac[i - 1] * i) % MOD; inv[n] = power(fac[n], MOD - 2); for (int i = n - 1; i >= 0; i--) { inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD; } auto C = [&](int n, int m) { return 1ll * fac[n] * inv[n - m] % MOD * inv[m] % MOD; }; auto g = [&](int i, int j) { int lim1 = n * (i + j) - i * j, lim2 = n * n - lim1; return 1ll * C(n, i) * C(n, j) % MOD * power(k - 1, lim1) % MOD * power(k, lim2) % MOD; }; auto f = [&](int x, int y) { int ret = 0; for (int i = x; i <= n; i++) { for (int j = y; j <= n; j++) { int v = 1ll * C(i, x) * C(j, y) % MOD * g(i, j) % MOD; if ((i + j - x - y) & 1) ret -= v, ret %= MOD, ret += MOD, ret %= MOD; else ret += v, ret %= MOD; } } return ret; }; std::cout << f(0, 0) << "\n"; } int main() { std::ios::sync_with_stdio(0); std::cin.tie(0); std::cout.tie(0); int t = 1; while (t--) { solve(); } return 0; }
sol:TBD
点击查看代码
#include <bits/stdc++.h> const int MAXN = 5005; const int MOD = 998244353; int dp[MAXN][MAXN]; void solve() { int n, m; std::cin >> n; m = n / 2; std::string s; std::cin >> s; std::vector <int> col(n + 1); for (int i = 1; i <= n; i++) col[i] = s[i - 1] - '0'; std::vector <std::vector<int> > adj(n + 1); for (int i = 1; i < n; i++) { int u, v; std::cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); } auto power = [&](int a, int b) { int ret = 1; for (; b; b >>= 1) { if (b & 1) ret = (1ll * a * ret) % MOD; a = (1ll * a * a) % MOD; } return ret; }; std::vector <int> fac(n + 1), inv(n + 1); fac[0] = 1; for (int i = 1; i <= n; i++) fac[i] = (1ll * fac[i - 1] * i) % MOD; inv[n] = power(fac[n], MOD - 2); for (int i = n - 1; i >= 0; i--) { inv[i] = (1ll * inv[i + 1] * (i + 1)) % MOD; } auto C = [&](int n, int m) { return 1ll * fac[n] * inv[n - m] % MOD * inv[m] % MOD; }; std::vector <std::vector<int> > siz(n + 1); for (int i = 0; i <= n; i++) siz[i].resize(2); auto dfs = [&](auto self, int u, int fa) -> void { siz[u][col[u]] = 1; dp[u][0] = 1; for (const auto &v : adj[u]) { if (v == fa) continue; self(self, v, u); int s1 = (siz[u][0] + siz[u][1]) / 2, s2 = (siz[v][0] + siz[v][1]) / 2, s3 = (siz[u][0] + siz[u][1] + siz[v][0] + siz[v][1]) / 2; std::vector <int> tmp(s3 + 1); for (int j = 0; j <= s1; j++) { for (int k = 0; k <= s2; k++) { if (j + k > m) break; tmp[j + k] += 1ll * dp[u][j] * dp[v][k] % MOD; tmp[j + k] %= MOD; } } siz[u][0] += siz[v][0], siz[u][1] += siz[v][1]; for (int i = 0; i <= s3; i++) dp[u][i] = tmp[i]; } for (int i = siz[u][col[u] ^ 1]; i >= 0; i--) { dp[u][i + 1] += 1ll * dp[u][i] * (siz[u][col[u] ^ 1] - i) % MOD; dp[u][i + 1] %= MOD; } }; dfs(dfs, 1, 0); auto g = [&](int x) { return 1ll * dp[1][x] * fac[m - x] % MOD; }; auto f = [&](int x) { int res = 0; for (int i = x; i <= m; i++) { int v = 1ll * C(i, x) * g(i) % MOD; if ((i - x) & 1) res -= v, res %= MOD, res += MOD, res %= MOD; else res += v, res %= MOD; } return res; }; for (int i = 0; i <= m; i++) { std::cout << f(i) << "\n"; } } int main() { std::ios::sync_with_stdio(0); std::cin.tie(0); std::cout.tie(0); int t = 1; while (t--) { solve(); } return 0; }
sol:TBD
点击查看代码
#include <bits/stdc++.h> const int MAXN = 1005; int dp[MAXN][MAXN][2][2]; void solve() { int n, k; std::cin >> n >> k; #define ll long long const int MOD = 1e9 + 7; std::vector <int> fac(n + 1), inv(n + 1); auto power = [&](int a, int b) { int ret = 1; for (; b; b >>= 1) { if (b & 1) ret = (1ll * ret * a) % MOD; a = (1ll * a * a) % MOD; } return ret; }; fac[0] = 1; for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % MOD; inv[n] = power(fac[n], MOD - 2); for (int i = n - 1; i >= 0; i--) { inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD; } auto C = [&](int n, int m) { return 1ll * fac[n] * inv[m] % MOD * inv[n - m] % MOD; }; dp[1][0][0][0] = dp[1][1][0][1] = 1; for (int i = 2; i <= n; i++) { for (int j = 0; j <= i; j++) { //select i - 1 if (j) { dp[i][j][0][0] += dp[i - 1][j - 1][0][0]; dp[i][j][0][0] %= MOD; dp[i][j][1][0] += dp[i - 1][j - 1][0][1]; dp[i][j][1][0] %= MOD; } //select i + 1 if (j) { dp[i][j][0][1] += dp[i - 1][j - 1][0][0]; dp[i][j][0][1] %= MOD; dp[i][j][0][1] += dp[i - 1][j - 1][1][0]; dp[i][j][0][1] %= MOD; dp[i][j][1][1] += dp[i - 1][j - 1][0][1]; dp[i][j][1][1] %= MOD; dp[i][j][1][1] += dp[i - 1][j - 1][1][1]; dp[i][j][1][1] %= MOD; } //select nothing dp[i][j][0][0] += dp[i - 1][j][1][0]; dp[i][j][0][0] %= MOD; dp[i][j][0][0] += dp[i - 1][j][0][0]; dp[i][j][0][0] %= MOD; dp[i][j][1][0] += dp[i - 1][j][0][1]; dp[i][j][1][0] %= MOD; dp[i][j][1][0] += dp[i - 1][j][1][1]; dp[i][j][1][0] %= MOD; } } auto g = [&](int x) { return 1ll * ((dp[n][x][1][0] + dp[n][x][0][0]) % MOD) * fac[n - x] % MOD; }; auto f = [&](int x) { int ret = 0; for (int i = x; i <= n; i++) { int v = 1ll * g(i) * C(i, x) % MOD; if ((i - x) & 1) ret -= v, ret %= MOD, ret += MOD, ret %= MOD; else ret += v, ret %= MOD; } return ret; }; std::cout << f(k) << "\n"; } int main() { std::ios::sync_with_stdio(0); std::cin.tie(0); std::cout.tie(0); int t = 1; while (t--) { solve(); } return 0; }
Timber
sol:TBD
点击查看代码
#include <bits/stdc++.h> void solve() { int n, m, k; std::cin >> n >> m >> k; const int MOD = 998244353; std::vector <int> fac(n + 1), inv(n + 1), pw(n + 1); auto power = [&](int a, int b) { int ret = 1; for (; b; b >>= 1) { if (b & 1) ret = (1ll * ret * a) % MOD; a = (1ll * a * a) % MOD; } return ret; }; fac[0] = 1; pw[0] = 1; for (int i = 1; i <= n; i++) { fac[i] = 1ll * fac[i - 1] * i % MOD; pw[i] = 1ll * pw[i - 1] * 2 % MOD; } inv[n] = power(fac[n], MOD - 2); for (int i = n - 1; i >= 0; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD; auto C = [&](int n, int m) { if (n < 0 || m < 0) return 0ll; if (m > n) return 0ll; return 1ll * fac[n] * inv[n - m] % MOD * inv[m] % MOD; }; #define ll long long int ans = 0; for (int i = 0; i <= m; i++) { ll tmp = (ll)n - 1ll * (m + i) * k; if (tmp < 0) continue; int v = 1ll * pw[m - i] * C(m, i) % MOD * C(n - (m + i) * k, m) % MOD; if (i & 1) ans -= v, ans %= MOD, ans += MOD, ans %= MOD; else ans += v, ans %= MOD; } std::cout << ans << "\n"; } int main() { std::ios::sync_with_stdio(0); std::cin.tie(0); std::cout.tie(0); int t = 1; while (t--) { solve(); } return 0; }
广义容斥(容斥系数)
本人认为二项式反演不等于广义容斥,相反,只是容斥中的一种可以简化的特殊情况,只是容斥系数的推导可以用到二项式反演。
例题
sol
设一个三角形由 个给定的三角形覆盖,那么有 。
发现只有为奇数时才有贡献,所以 。
考虑用二项式定理相减化简,具体来说如下。
所以有 。
然后枚举子集计算子集中三角形面积交乘上系数相加即可。
还有由于是等腰直角三角形,比较特殊,无需计算几何,初中知识即可。
点击查看代码
#include <bits/stdc++.h> #define ll long long struct Tri { int x, y, r; double size() { if (r < 0) return 0.0; return 1.0 * r * r / 2.0; } Tri(int X = 0, int Y = 0, int R = 0) { x = X; y = Y; r = R; } bool operator == (const Tri &t) const { return x == t.x && y == t.y && r == t.r; }; }; Tri merge(Tri x, Tri y) { int c1 = x.x + x.y + x.r, c2 = y.x + y.y + y.r; int mxx = std::max(x.x, y.x), mxy = std::max(x.y, y.y); return Tri(mxx, mxy, std::min(c1, c2) - mxx - mxy); } void solve() { int n; std::cin >> n; std::vector <Tri> a(n); for (int i = 0; i < n; i++) { std::cin >> a[i].x >> a[i].y >> a[i].r; } std::vector <int> f(n + 1); f[1] = 1; for (int i = 2; i <= n; i++) { f[i] = -2 * f[i - 1]; } double ans = 0; for (int mask = 1; mask < (1 << n); mask++) { int sz = __builtin_popcount(mask); Tri tr = Tri(0, 0 ,0); for (int i = 0; i < n; i++) { if (mask & (1 << i)) { if (tr == Tri(0, 0, 0)) tr = a[i]; else tr = merge(tr, a[i]); } } double s = tr.size(); if (s) { ans += 1.0 * f[sz] * s; } } std::cout << std::fixed << std::setprecision(1) << ans << "\n"; // printf("%.1lf\n", ans); } int main() { std::ios::sync_with_stdio(0); std::cin.tie(0); std::cout.tie(0); int t = 1; while (t--) { solve(); } return 0; }
sol
没必要写了,如果写一遍多半会和别人的题解大幅度相似点击查看代码
#include <bits/stdc++.h> const int MAXN = 3005; const int MOD = 998244353; #define ll long long int C[MAXN][MAXN], f1[MAXN], f2[MAXN], pw[MAXN * MAXN]; void Mod(int &x) { if (x >= MOD) x -= MOD; if (x < 0) x += MOD; } void Mod(ll &x) { if (x >= MOD) x -= MOD; if (x < 0) x += MOD; } void init() { for (int i = 0; i < MAXN; i++) { for (int j = 0; j <= i; j++) { if (!j) C[i][j] = 1; else C[i][j] = C[i - 1][j] + C[i - 1][j - 1], Mod(C[i][j]); } } pw[0] = 1; for (int i = 1; i < MAXN * MAXN; i++) pw[i] = pw[i - 1] + pw[i - 1], Mod(pw[i]); } void solve(int n, int m, int a, int b) { int t = std::max(n, m); for (int i = 0; i <= t; i++) { f1[i] = f2[i] = 0; } f1[a] = f2[b] = 1; for (int i = a + 1; i <= n; i++) { for (int j = a; j < i; j++) { f1[i] -= 1ll * C[i - 1][j - 1] * f1[j] % MOD; Mod(f1[i]); } } for (int i = b + 1; i <= m; i++) { for (int j = b; j < i; j++) { f2[i] -= 1ll * C[i - 1][j - 1] * f2[j] % MOD; Mod(f2[i]); } } ll ans = 0; for (int i = a; i <= n; i++) { for (int j = b; j <= m; j++) { ans += 1ll * f1[i] * f2[j] % MOD * C[n][i] % MOD * C[m][j] % MOD * pw[(n - i) * (m - j)] % MOD; Mod(ans); } } std::cout << ans << "\n"; } int main() { std::ios::sync_with_stdio(0); std::cin.tie(0); std::cout.tie(0); init(); int n, m, a, b; while (std::cin >> n >> m >> a >> b) { solve(n, m, a, b); } return 0; }
HDU5072 Coprime
TBD
莫比乌斯反演
link,这玩意儿严格来讲属于数论了,不在此展开。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】