avatar
fireworks99
keep hungry keep foolish

POJ 3904 Sky Code 容斥原理

Description

给出数字N,给出N个数字,从中找出四个数字,它们的公因数只有“1”,问能找到多少组?

Analyze

测试四个数字的公因数是否只有“1”并不好实现,要检测多次。

试着从 (是 = 总 - 非) 方面想

对每个数字进行①质因数分解(准公因子)

同时记录下②不同质因子之间相乘所构成的重公因子

统计所有③准公因子与重公因子出现的次数(假设为num[i])

计算④组合数C(num[i], 4) 即为以num[i]为公因子的四个数的组合数目

但是在这里面,计算2、3、6会有重复,于是便有了容斥

Code

#include <map>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll __int64

int factor[10005], cnt = 0;
map<int, int> Count;
map<int, int> Num;

void get_factors(int n)
{
    cnt = 0;
    for(int i = 2; i * i <= n; ++i)
    {
        if(n % i == 0)
        {
            factor[cnt++] = i;
            while(n % i == 0)
                n /= i;
        }
    }
    if(n != 1)
        factor[cnt++] = n;
}

void Rongchi(int n)///队列版
{
    get_factors(n);
    int t = 0, q[10005], flag[10005];
    q[t++] = 1;
    memset(flag, -1, sizeof(flag));
    for(int i = 0; i < cnt; ++i)
    {
        int up = t;
        for(int j = 0; j < up; ++j)///一层循环一种集合
        {
            q[t] = q[j] * factor[i];
            Count[ q[t] ]++;
            flag[t] = -flag[j];
            Num[ q[t] ] = (flag[t] == 1 ? 1 : 0);
            t++;
        }
    }
}

void Rongchi(int n)///二进制版
{
    get_factors(n);
    for(int i = 1; i < (1 << cnt); ++i)///一层循环一种集合
    {
        int tem = 1, tot = 0;
        for(int j = 0; j < cnt; ++j)
            if(i & (1 << j))
            {
                tot++;
                tem *= factor[j];
            }
        Count[tem]++;
        Num[tem] = tot;
    }
}

///num : 当前针对第几个元素
///used : 在此之前拿取了多少个元素
///up : 目标是取几个元素
///mu : 已取元素之积

void DFS(int num, int used, int up, int mu)
{
    if(used == up)
    {
        Count[mu]++;
        if(up & 1)
            Num[mu] = 1;
        else
            Num[mu] = 0;
        return;
    }
    if(num == cnt)
        return ;
    DFS(num + 1, used, up, mu);
    DFS(num + 1, used + 1, up, mu * factor[num]);
}

void Rongchi(int n)///DFS版
{
    get_factors(n);
    int mu = 1;
    for(int i = 1; i <= cnt; ++i)///子集内含有的元素个数(实现奇加偶减)
        DFS(0, 0, i, 1);
}

ll C(ll n)///这里n虽不到10000,若用int,n^4会爆
{
    return n * (n - 1) * (n - 2) * (n - 3) / 24;
}

int main()
{
    int n, a;
    while(~scanf("%d", &n))
    {
        Count.clear();
        for(int i = 0; i < n; ++i)
        {
            scanf("%d", &a);
            Rongchi(a);
        }
        ll ans = 0;
        map<int, int> ::iterator it = Count.begin();
        for(; it != Count.end(); ++it)
        {
            int idx = it -> first;
            if(Num[idx] & 1)
                ans += C(Count[idx]);
            else
                ans -= C(Count[idx]);
        }
        cout << C(n) - ans << '\n';
    }
    return 0;
}

实现容斥的三种模板

①队列式

巧妙地运用了负负得正来决定奇加偶减

②二进制

巧妙地运用了一个n元集合有 2 ^ n 个子集的性质,去掉空集从1开始,保留全集到2 ^ n结束,对这2 ^ n个数,以一个自增计数器 i 的每个二进制形式决定每个集合的构成。从 i 的二进制形式的右边向左边看,第 j 位上有1表示当前集合含有全集里的第 j 个元素,那么i的二进制里1的个数自然而然地反映了当集合内的元素个数,实现奇加偶减

③.DFS

直接对子集中含有元素的个数进行枚举,可根据自增变量实现奇加偶减

Site by Baole Zhao | Powered by Hexo | theme PreciousJoy