容斥原理小结

CF451E Devu and Flowers

若没有个数限制, 则可以用插板法解决
有个数限制:
设第 i 个箱子里选了 x_i 朵花
设集合 S_{s} 表示集合 s 中的花的数量 > a_i 的集合
则 |S_{s}|=C_{m+n-1-∑{(a_j+1),j∈s}}^{n-1}
组合数用定义求

点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <assert.h>

using namespace std;

typedef long long LL;

const int N = 25;

const LL mod = 1e9 + 7;

LL n, m;
LL a[N];

LL qpow(LL b, LL p) {
	LL res = 1;
	while(p) {
		if(p & 1) res = res * b % mod;
		b = b * b % mod, p >>= 1;
	}
	return res;
}

LL C(LL a, LL b) {
	if(a < b) return 0;
	LL up = 1, down = 1;
	for(register LL i = a, j = 1; j <= b; i --, j ++) {
		up = up * (i % mod) % mod;
		down = down * j % mod;
	}
	return up * qpow(down, mod - 2) % mod;
}

int main() {
	scanf("%lld%lld", &n, &m);
	for(int i = 0; i < n; i ++)
		scanf("%lld", a + i);
	LL res = 0;
	for(register LL s = 0; s < (1 << n); s ++) {
		int cnt = 0;
		LL sum = m + n - 1;
		for(register int i = 0; i < n; i ++)
			if(s >> i & 1) {
				sum -= a[i] + 1;
				cnt ++;
			}
		if(cnt & 1) res -= C(sum, n - 1);
		else res += C(sum, n - 1);
		res = (res % mod + mod) % mod;
	}
	printf("%lld\n", res);
	return 0;
}

BZOJ2169 连边

DP: 设 f[i][j] 表示添加 i 条边后有 j 个度数为奇数的点的方案数
f[i][j] = (f[i-1][j-2] * C2(n-(j-2),2) -> 连接 偶点 和 偶点
+ f[i-1][j] * (n-j) * j -> 连接 奇点 和 偶点
+ f[i-1][j+2] * C2(j+2, 2) -> 连接 奇点 和 奇点
- f[i-2][j] * (C2(n,2)-i+2)) / i -> 减掉 重边
减去一项是减掉重边的, 除以 i 是除掉重复计算的

点击查看代码

#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

typedef long long LL;
const int mod = 10007, N = 1e3 + 5;

int inv[N]; // 逆元
LL f[N][N];
int n, m, k, d[N]; // d[u] 为 u 的度数

int qpow(int b, int p) { // 快速幂
	int res = 1;
	while(p) {
		if(p & 1) res = res * b % mod;
		b = b * b % mod, p >>= 1;
	}
	return res;
}
inline int C2(int x) { return x * (x - 1) / 2; } // C(n,2)

int main() {
	for(int i = 1; i <= 1000; i ++)
		inv[i] = qpow(i, mod - 2);
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 1, a, b; i <= m; i ++) {
		scanf("%d%d", &a, &b);
		d[a] ++, d[b] ++;
	}
	int cnt = 0;
	for(int i = 1; i <= n; i ++)
		if(d[i] % 2) cnt ++;
	f[0][cnt] = 1; // 初始条件
	for(int i = 1; i <= k; i ++) {
		for(int j = 0; j <= n; j ++) {
			if(j - 2 >= 0) (f[i][j] += f[i - 1][j - 2] * C2(n - j + 2) % mod) %= mod;
			if(j + 2 <= n) (f[i][j] += f[i - 1][j + 2] * C2(j + 2) % mod) %= mod;
			(f[i][j] += f[i - 1][j] * (n - j) * j % mod) %= mod;
			if(i >= 2) (f[i][j] = f[i][j] - f[i - 2][j] * (C2(n) - i + 2) % mod + mod) %= mod;
			(f[i][j] *= inv[i]) %= mod;
		}
	}
	printf("%lld\n", f[k][0]);
	return 0;
}

P1450 [HAOI2008] 硬币购物

点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

const int N = 100005;

int c[5], n;
int d[5], s;
long long f[N];

int main() {
	scanf("%d%d%d%d%d", c, c + 1, c + 2, c + 3, &n);
	f[0] = 1;
	for(int i = 0; i < 4; i ++)
		for(int j = c[i]; j <= 100000; j ++)
			f[j] += f[j - c[i]];
	while(n --) {
		scanf("%d%d%d%d%d", d, d + 1, d + 2, d + 3, &s);
		long long res = 0;
		for(int S = 0; S < 16; S ++) {
			int cnt = 0; int tmp = s;
			for(int i = 0; i < 4; i ++)
				if((S >> i) & 1) {
					cnt ++;
					tmp -= (long long)c[i] * (d[i] + 1);
				}
			if(tmp >= 0)
				if(cnt & 1) res -= f[tmp];
				else        res += f[tmp];
		}
		printf("%lld\n", res);
	}
	return 0;
}

P2567 [SCOI2010]幸运数字

点击查看代码
// 使用剪枝的搜索解决容斥原理的问题
#include <stdio.h>
#include <algorithm>

using namespace std;

typedef long long LL;

LL number[2048], tot; // 好数字至多 2046 个
bool del[2048]; // 是否被删掉(如果好数字中有这个数的倍数, 则标记)
LL num2[2048], cnt2; // 被删掉后剩下的好数, 只剩下 943 个数(剪枝 1)
LL ans; // 答案
LL L, R;

// LL gcd(LL x, LL y) {
// 	while(x) {
// 		y %= x;
// 		x ^= y ^= x ^= y;
// 	}
// 	return y;
// }
#define gcd __gcd

inline int calc(LL l, LL r, LL d) { // 计算 [l, r] 中有多少个 d 的倍数
	return r / d - (l - 1) / d;
}
// 使用搜索解决容斥原理问题     在搜索时如果 lcm 大于 R 则剪掉(剪枝 2)
// u 表示搜到第几个 num2, cnt 表示选的数的个数, lcm 表示当前的最小公倍数
void dfs(int u, int cnt, LL lcm) {
	if(lcm > R) return; // 剪枝
	if(u > cnt2) {
		if(cnt) { // 排掉不选的情况
			if(cnt & 1) ans += calc(L, R, lcm);
			else ans -= calc(L, R, lcm);
		}
	} else {
		dfs(u + 1, cnt, lcm); // 不选这个数
		LL tmp = lcm / gcd(lcm, num2[u]);
		if(double(tmp) * num2[u] <= R) // 小心爆 LL
			dfs(u + 1, cnt + 1, tmp * num2[u]); // 选这个数
	}
}

int main() {
	for(int n = 1; n <= 10; n ++) {
		for(int s = 0; s < (1 << n); s ++) {
			LL num = 0;
			for(int i = 0; i < n; i ++)
				num = (num << 1) + (num << 3) + 6 + ((s >> i & 1) << 1);
			number[++ tot] = num;
		}
	}
	sort(number + 1, number + tot + 1);
	for(int i = 1; i <= tot; i ++)
		for(int j = i + 1; j <= tot; j ++)
			if(number[j] % number[i] == 0) del[j] = true;
	for(int i = 1; i <= tot; i ++)
		if(!del[i]) num2[++ cnt2] = number[i];
	reverse(num2 + 1, num2 + cnt2 + 1); // 剪枝 3 : 从大到小排序增加搜索效率
	scanf("%lld%lld", &L, &R);
	dfs(1, 0, 1);
	printf("%lld\n", ans);
	return 0;
}

P5505 [JSOI2011] 分特产

待做

[JSOI2015]染色问题

P5664 CSP-S2019 Emiya 家今天的饭

容斥原理+DP 答案=没有限制3的答案-∑某一列不满足性质3的答案, 记号:行表示方案,纵表示食材
第2类: 不满足性质3的只有一种列(必须>k/2)于是求和只需枚举列超过1/2的,令枚举的这列为c
设f[i][j][k]表示前i行,第c列选j个,其余列选k个的方案数(其中s[i]表示第i行的和)
转移: f[i][j][k] = f[i-1][j][k] + a[i][c]f[i-1][j-1][k] + (s[i]-a[i][c])f[i-1][j][k-1]
优化: f[i][t]表示前i行,j-k=t的方案数,统计答案时只找t>0的 ; 初始: f[0][0] = 1
转移: f[i][t] = f[i-1][t] + a[i][c]f[i-1][t-1] + (s[i]-a[i][c])f[i-1][t+1]
第1类: 类似前面的转移;设g[i][j]表示前i行选j个的方案数 ; 初始: g[0][0] = 1
转移: g[i][j] = g[i-1][j] + s[i]*g[i-1][j-1]

点击查看代码


#include <stdio.h>
#include <string.h>
const int N = 105, M = 2e3 + 5, mod = 998244353;
typedef long long LL;
int n, m, a[N][M], s[N];
int f[N][N << 1], g[N][N];
int ans;
int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++)
			scanf("%d", &a[i][j]), (s[i] += a[i][j]) >= mod && (s[i] -= mod);
	for(int c = 1; c <= m; c ++) {
		memset(f, 0, sizeof(f)), f[0][n] = 1; // 由于t可能是负数,将下标+n
		for(int i = 1; i <= n; i ++)
			for(int t = n - i; t <= n + i; t ++)
				f[i][t] = (f[i-1][t] + (LL)a[i][c] * f[i-1][t-1] % mod + LL(s[i]-a[i][c]+mod) * f[i-1][t+1] % mod) % mod;
		for(int i = 1; i <= n; i ++) (ans += f[n][n + i]) >= mod && (ans -= mod);
	}
	(ans = mod - ans) >= mod && (ans -= mod), g[0][0] = 1;
	for(int i = 1; i <= n; i ++) {
		g[i][0] = g[i - 1][0];
		for(int j = 1; j <= n; j ++)
			g[i][j] = (g[i - 1][j] + (LL)s[i] * g[i - 1][j - 1] % mod) % mod;
	}
	for(int i = 1; i <= n; i ++) (ans += g[n][i]) >= mod && (ans -= mod);
	printf("%d\n", ans);
	return 0;
}

posted @ 2022-09-29 09:50  azzc  阅读(20)  评论(0编辑  收藏  举报