【專題總結】容斥原理(持續更新)
從動機的角度出發。在用“做減法”的思想解決計數類問題時,可能會遇到“多減去符合條件的數目”,試圖加回來的時候又會遇到“多加上不符合條件的數目”的情況,這時候也許需要用容斥原理來設計計數演算法。
從實現的角度出發。在對事件集合的“並事件”計數遇到困難時,可通過容斥原理轉化成對事件子集的“交事件”的計數。即“大並化小交”)
UVAOJ 11806
大意
將
思路
根據“做減法”的思想,我們能夠起草一種貌似正確的演算法:將無任何限制的擺放棋子的方法數
程式碼
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e6 + 7, maxk = 500;
int t, n, m, k, r, c, b, ans, C[maxk+5][maxk+5];
// 預處理出組合數
void makeComb(int n) {
memset(C, 0, sizeof(C));
for(int i = 0; i <= n; i++) {
C[i][0] = C[i][i] = 1;
for(int j = 1; j < i ; j++) {
C[i][j] = (C[i-1 ][j] + C[i-1][j-1]) % mod;
}
}
}
int main() {
makeComb(maxk);
scanf("%d", &t);
for(int kase = 1; kase <= t; kase++) {
scanf("%d%d%d", &n, &m, &k);
ans = 0;
// 列舉壓縮後的狀態
for(int s = 0; s < 16; s++) {
r = n;
c = m;
b = 0;
if(s & 1) {
r--;
b++;
}
if(s & 2) {
r--;
b++;
}
if(s & 4) {
c--;
b++;
}
if(s & 8) {
c--;
b++;
}
// 奇加偶減
if(b & 1) {
ans = (ans - C[r*c][k] + mod) % mod;
}
else {
ans = (ans + C[r*c][k]) % mod;
}
}
printf("Case %d: %d\n", kase, ans);
}
return 0;
}
HDU 5120
大意
求平面直角座標系上的兩個圓環的相交部分的面積。
思路
一個圓環由一個大圓和一個小圓構成。將兩個相交圓環在紙上畫出來後,兩個圓環相交部分的面積實際上等於兩個大圓的相交面積減去兩個“大圓與小圓”的相交面積,再加回兩個小圓的相交面積就是最後的結果。這是容斥原理的一種特殊情況,因此可以直接得出簡短的公式並將數值代入進行計算。
程式碼
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-10, pi = 4 * atan(1.0);
int dcmp(double x) {
if(fabs(x) < eps) return 0;
return x < 0 ? -1 : 1;
}
struct Point {
double x, y;
Point() {}
Point(double x, double y): x(x), y(y) {}
double distance(Point p) {
return hypot(x - p.x, y - p.y);
}
};
struct Circle {
double r;
Point o;
Circle() {}
Circle(double r, Point o): r(r), o(o) {}
double area(Circle c) {
double d = o.distance(c.o);
if(dcmp(d - r - c.r) >= 0) {
return 0;
}
if(dcmp(d - fabs(r - c.r)) <= 0) {
double R = dcmp(r - c.r) < 0 ? r : c.r;
return pi * R * R;
}
double x = (r * r + d * d - c.r * c.r) / (2 * d);
double a1 = acos(x / r);
double a2 = acos((d - x) / c.r);
double s1 = a1 * r * r + a2 * c.r * c.r;
double s2 = r * d * sin(a1);
return s1 - s2;
}
};
int main() {
int t;
scanf("%d", &t);
double r, R, x1, y1, x2, y2;
for(int kase = 1; kase <= t; kase++) {
scanf("%lf%lf%lf%lf%lf%lf", &r, &R, &x1, &y1, &x2, &y2);
Point o1(x1, y1), o2(x2, y2);
Circle c1(r, o1), C1(R, o1);
Circle c2(r, o2), C2(R, o2);
double s1 = C1.area(C2);
double s2 = C1.area(c2);
double s3 = c1.area(c2);
printf("Case #%d: %.6f\n", kase, s1 - 2 * s2 + s3);
}
return 0;
}
HDU 4135
大意
問區間
思路
由於
於是嘗試換一個方向思考,能否求出“不互質”的情況,再讓總情況減去“不互質”的情況呢?嘗試思考“不互質”的本質。因為“整數的結構是由質因數決定的”,因此“不互質”的本質就是“有公因數”。所以,我們將問題轉化為“問區間
現在當務之急是找出
程式碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t, n, m, cnt, full;
ll a, b, pro, ans;
vector <int> f;
ll solve(ll x) {
ll res = 0;
for(int i = 1; i <= full; i++) {
int pro = 1;
int cnt = 0;
for(int j = 0; j < m; j++) {
if((i >> j) & 1) {
pro *= f[j];
cnt++;
}
}
if(cnt & 1) {
res += x / pro;
}
else {
res -= x / pro;
}
}
return x - res;
}
int main() {
scanf("%d", &t);
for(int kase = 1; kase <= t; kase++) {
scanf("%I64d%I64d%d", &a, &b, &n);
f.clear();
for(int i = 2; i * i <= n; i++) {
if(n % i == 0) {
f.push_back(i);
while(n % i == 0) {
n /= i;
}
}
}
if(n != 1) {
f.push_back(n);
}
m = f.size();
full = (1 << m) - 1;
printf("Case #%d: %I64d\n", kase, solve(b) - solve(a - 1));
}
return 0;
}
HDU 5768
大意
給定一個區間
思路
本題實際上要我們求對於一個給定的