容斥与简单反演乱写

#define TBD ToBeDone 😅

容斥的本质

构造一组数fi,使得 i=0hx(hxi)fi=gx,其中gx为希望x这个元素被统计的次数,fi为容斥系数,hix超集的大小。

可以理解对于一个需要统计的元素x为枚举包含其集合s的大小i,对于所有大小为is集合将其中元素都统计fi次,使得最后x被恰好统计x次。

对于常规的容斥题,有gx=1,此时fi=(1)(i1),可以带入用二项式定理验证。

例题

联考题 装饰

sol

image

点击查看代码
#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;
}

[ARC101E] Ribbons on Tree

sol 三个月前做过,现在不会了,不想某位神仙两年前做过现在一眼秒了😅

比较navie的想法是设dpu,xu的子树中有x个点未被覆盖,需要子树外一个点向内某个点相连的方案数,直接类似树背包转移有 dpu,x+y2k=vson(u),x+y>0,kmin(x,y)dpu,x×dpv,y×(xk)×(yk)×k!

系数过于复杂,不便于优化。看到每条边至少被覆盖一次考虑容斥。

考虑边集S中的边一次都没被覆盖过,这等价于删去这些边。此时整张图被分成了若干个联通块,显然每个联通块内部匹配的方案数是 g(n)=2kn,kN2k1,注意n为奇数时g(n)=0

数据范围无法枚举子集,考虑dp,重定义dpu,x为以u为根的子树联通块大小为x的方案数(考虑容斥系数)。那么有这条边断掉 dpu,x×dpv,y×g(y)×1dpu,x (断边集合大小加一),保留这条边 dpu,x×dpv,ydpu,x+y

最后答案即为 i=1ndp1,i×g(i)。复杂度为树形背包的 O(n2)

点击查看代码
#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;
}

子集反演

g(s)=tsf(t),那么有 f(s)=ts(1)(|s||t|)g(t)。(子集形式)

g(s)=stf(t),那么有 f(s)=st(1)(|t||s|)g(t)。(超集形式)

image

例题

[ZJOI2016] 小星星

TBD

[SHOI2016] 黑暗前的幻想乡
TBD

二项式反演

gn=i=0n(ni)fi,那么有fn=i=0n(1)(ni)(ni)gi

gn=i=nm(in)fi,那么有fn=i=nm(1)(in)(in)gi

证明:咕咕咕。

发现两种形式左右都是对称的,且-1的次数都为|in|便于背诵

本质是容斥在大小相同的集合贡献相同的一种特殊形式。

一般设fn为恰好取n个的值,gn为钦定取n个的值,通常情况下后者比前者更好计算,就完成了从至少到恰好的转化

例题

Another Filling the Grid

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;
}

[NOI Online #2 提高组] 游戏

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;
}

Positions in Permutations

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;
}

广义容斥(容斥系数)

本人认为二项式反演不等于广义容斥,相反,只是容斥中的一种可以简化的特殊情况,只是容斥系数的推导可以用到二项式反演。

i=0hx(hxi)fi=gxfx=i=0hx(1)(hxi)(hxi)gi

例题

[COCI2009-2010#6] XOR

sol

设一个三角形由 t 个给定的三角形覆盖,那么有 ft=i=0t(1)(ti)(ti)[2t]

发现只有为i奇数时才有贡献,所以 ft=(1)(t1)i=0t(ti)[2t]

考虑用二项式定理相减化简,具体来说如下。

(a+b)n(ab)n=i=0n(ni)anibii=0n(1)i(ni)anibi=2×i=0n[2n](ni)anibi

所以有 ft=(1)(t1)(1+1)t(11)t2=(2)(t1)

然后枚举子集计算子集中三角形面积交乘上系数相加即可。

还有由于是等腰直角三角形,比较特殊,无需计算几何,初中知识即可。

点击查看代码
#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;
}

HDU-6314 Matrix

sol 没必要写了,如果写一遍多半会和别人的题解大幅度相似

image

点击查看代码
#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,这玩意儿严格来讲属于数论了,不在此展开。

posted @   cqbzlzh  阅读(17)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示