容斥原理

来源:互联网 发布:好听的淘宝女店铺名称 编辑:程序博客网 时间:2024/06/05 10:46

引入

在1到100的数中,有多少个数是2、3、5的倍数?

这是一道小学生数学题,通常我们都是先将1到100中2、3、5的倍数的个数分别算出来,求和,然后减去6(2*3)的倍数,10(2*5)的倍数,15(3*5)的倍数的个数,最后再加上30(2*3*5)的倍数个数,就可以得到最终的答案。

其实,将上面的计算方法抽象,就可以得到逆天的容斥原理:容斥原理又称排容原理,例如在两个集的情况时,我们可以透过将 |A|和 |B|相加,再减去其交集的基数,而得到其并集的基数。

这里写图片描述
先看一下上面这张图,整张图的面积 = A + B + C - A and B - B and C - A and C + A and B and C。

容斥原理的作用主要体现在:当直接求一个值比较难求时,我们就可以用容斥原理转换成另一个易求的数值。

例题

                                             矩形相交面积

题目描述
给出n(n <= 10)个矩形,求它们的总面积,注意会有相交部分。
这题是最基础的容斥原理(离散化更简单),几个矩形相交,做的方法有多种。

思路:

1、染色

开一个(bool)二维数组,代表整个坐标系。在输入矩形时,用双重for循环把数组中对应矩形的部分定为true,结束时用ans记录数组内true的个数即可,但这种方法太过暴力:空间O(4*10^8),时间O(4*10^9)。

2、分割(离散化)

如图,将每一个矩形分成一些小矩形,并逐一判断这些小矩形的是否在大矩形中。时间复杂度:O(N^2),空间复杂度:O(N^2)。

这里写图片描述

3、容斥原理

根据容斥原理,我们可以得到最后的ans就等于:

A的面积,B的面积,C的面积…… - A和B的重叠部分,B和C的重叠部分,A和C的重叠部分…… + A和B和C的重叠部分 …… ……

要实现枚举完这些状态,可以搜索,也可以二进制枚举。

#include <bits/stdc++.h>using namespace std;struct Map {    int x1, y1;    int x2, y2;}m[20], now;int a[20];bool G(int y) {    //cout<<y<<endl;    //printf("%d %d %d %d\n", now.x1, now.y1, now.x2, now.y2);    const int weixie = false;    now.x1 = max(now.x1, m[y].x1);    now.y1 = min(now.y1, m[y].y1);    now.x2 = min(now.x2, m[y].x2);    now.y2 = max(now.y2, m[y].y2);    //printf("%d %d %d %d\n", now.x1, now.y1, now.x2, now.y2);    if(now.x1 > now.x2 || now.y1 < now.y2)        return weixie;    return true;}int Get(const int x) {    if(x%2 == 0) return -1;    else return 1;}bool _sort(Map x, Map y) {    return (x.x1 < y.x1) || (x.x1 == y.x1 && x.y1 > y.y1);}void Change(int x) {    for( int t=x, s=1; t; t/=2, s++) {        a[s] = t % 2;    }}int main() {    freopen( "1865.in" , "r", stdin );    freopen( "1865.out", "w", stdout);    int n, ans = 0; scanf("%d", &n);    for( int i=1; i<=n; i++) {        scanf("%d %d", &m[i].x1, &m[i].y1);        scanf("%d %d", &m[i].x2, &m[i].y2);        //ans += (m[i].y1 - m[i].y2) * (m[i].x2 - m[i].x1);    }    sort(m+1, m+n+1, _sort);    for( int i=1; i<(1<<n); i++) {        Change(i);        int t = 0, s = 0;        for( int j=1; j<=n; j++)            if(a[j] == 1) {                s = j;                break;             }        for( int j=1; j<=n; j++)            if(a[j] == 1) t ++;        now.x1 = m[s].x1, now.x2 = m[s].x2;        now.y1 = m[s].y1, now.y2 = m[s].y2;        bool flag = true;        for( int j=1; j<=n; j++)            if(a[j] == 1) {                //printf("%d %d %d %d\n", now.x1, now.y1, now.x2, now.y2);                if(G(j) == false) {                    flag = false;                    break;                }                //printf("%d %d %d %d\n", now.x1, now.y1, now.x2, now.y2);            }        //printf("%d\n", flag);        if(flag != false)            ans += (now.y1 - now.y2) * (now.x2 - now.x1) * Get(t);    }    printf("%d\n", ans);    return 0;}

附:
计算矩形重复面积方法如下:

bool Get(int y) {    now.x1 = max(now.x1, m[y].x1);    now.y1 = min(now.y1, m[y].y1);    now.x2 = min(now.x2, m[y].x2);    now.y2 = max(now.y2, m[y].y2);    if(now.x1 > now.x2 || now.y1 < now.y2)        return false;    return true;}

详见下图:
这里写图片描述