POJ 3904 Sky Code(容斥原理)

本文探讨了一道经典的算法题目,即在给定的一组正整数中寻找互质的四元组数量。通过质因数分解和容斥原理的应用,提供了一种高效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Sky Code

Stancu likes space travels but he is a poor software developer and will never be able to buy his own spacecraft. That is why he is preparing to steal the spacecraft of Petru. There is only one problem – Petru has locked the spacecraft with a sophisticated cryptosystem based on the ID numbers of the stars from the Milky Way Galaxy. For breaking the system Stancu has to check each subset of four stars such that the only common divisor of their numbers is 1. Nasty, isn’t it? Fortunately, Stancu has succeeded to limit the number of the interesting stars to N but, any way, the possible subsets of four stars can be too many. Help him to find their number and to decide if there is a chance to break the system. 

Input
In the input file several test cases are given. For each test case on the first line the number N of interesting stars is given (1 ≤ N ≤ 10000). The second line of the test case contains the list of ID numbers of the interesting stars, separated by spaces. Each ID is a positive integer which is no greater than 10000. The input data terminate with the end of file.
Output
For each test case the program should print one line with the number of subsets with the asked property.
Sample Input

4
2 3 4 5 
4
2 4 6 8 
7
2 3 4 5 7 6 8

Sample Output

1 
0 
34
题意:

问给n个数,问这n个数中可以做成多少个四元组使得四个数互质,注意每两个数不用两两互质

分析:

我们知道n个数总的可以组成的四元组的个数为C4nCn4

因为直接求四个数互质的四元组有多少个比较难求,因此我们转而求不互质的四元组有多少个,即公因数为d的(d>1)的四元组,然后最终用C4nCn4减去

那么不互质的四元组有多少个怎么求呢?

我们首先可以想到公因数为d那么d到底是几?它可以是任何大于1的数,所以我们从2开始枚举d

那么确定下了d后我们知道四元组中四个数如果gcd是d的话那么说明四个数的因数得含有的d,因此我们需要知道给定的n个数中到底有几个数它的因子有d,一旦我们能知道有多少个数因子含有d,那么我们就可以用组合数算出这样的四元组的个数,假设含有因数d的数字有cnt个,那么可以构成的四元组就有C4cntCcnt4

这个cnt怎么求呢?

对于每个给定的数我们可以对其进行质因数分解,得到不同的质因数,然后利用二进制枚举所有不同的组合方式,得到因数,说明这个数包含了这个因数,那么包含这个因数的数字的个数就可以加1,我们可以用cnt数组记录,cnt[i]表示含有因子i的数有多少。

那么知道了每种gcd为d(d>1)的四元组个数了,总的加起来就可以了吗?

当然不是因为里面会有重复

举个例子我们会枚举公因数为2的,为3的,为6的四元组的个数而公因数为6的四元组中一定有公因数为2和3的四元组,因为6=2×3,所以造成了重复

为了解决这个问题才引出这道题的主要算法

容斥定理

当公因数d中含有奇数个不同的质因子的时候为加,而为偶数个不同的质因子的时候为减,因为正如上面例子,奇数的时候(从1开始只含一个质因子)作为开始,偶数的时候就会出现重复,这样使用容斥定理就能消除多加多减的影响。

而记录因数d含有多少个不同质因子在上面二进制枚举质因子组合的时候顺便记录下来就好了

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e4+10;
int n,m;
int cnt[maxn];//cnt[i]记录所给数据中,含有因数i的个数有几个
int num[maxn];//num[i]记录所给数据中,因数i中含有几个不同素因子
ll c[maxn];//c[i]表示组合数C(i,4)
int prime[maxn];
void init(){
    for(ll i = 4; i < maxn; i++){
        c[i] = i * (i - 1) * (i - 2) * (i - 3) / 24;
    }
}
void divide(int n){
    int tot = 0;
    for(int i = 2; i * i <= n; i++){
        if(n % i == 0){
            prime[tot++] = i;
            while(n % i == 0) n /= i;
        }
    }
    if(n > 1) prime[tot++] = n;//分解质因子
    for(int i = 1; i < (1<<tot); i++){//枚举质因子组合情况得到所有可能因子
        int tmp = 1;
        int sum = 0;
        for(int j = 0; j < tot; j++){
            if(i & (1 << j)){
                tmp *= prime[j];
                sum++;
            }
        }
        cnt[tmp]++;//说明数n包含tmp这种因子,所以个数+1
        num[tmp] = sum;//tmp这种因子内含有sum个不同质因子
    }
}
int main(){
    init();
    memset(num,0,sizeof(num));
    while(scanf("%d",&n) != EOF){
        memset(cnt,0,sizeof(cnt));
        for(int i = 0; i < n; i++){
            scanf("%d",&m);
            divide(m);//质因子分解统计相关数据
        }
        ll ans = 0;
        for(int i = 2; i < maxn; i++){
            if(cnt[i] >= 4){
                if(num[i] & 1) ans += c[cnt[i]];//因子i中含有的质因子数为奇数加
                else ans -= c[cnt[i]];//否则减
            }
        }
        printf("%lld\n",c[n]-ans);
    }
    return 0;
}
<think>嗯,用户想了解如何在C语言中实现容斥原理的应用,并且需要示例代码。首先,我需要回忆一下容斥原理的基本概念。容斥原理通常用于计算多个集合的并集大小,通过交替加减不同集合的交集来避免重复计算。这在组合数学和算法问题中很常见,比如计算满足某些条件的数的个数。 接下来,我得想想如何在C语言中具体实现这个原理。通常,容斥原理的实现会涉及到遍历所有可能的子集组合,然后根据子集的大小来决定是加还是减。例如,对于m个集合,需要遍历从1到2^m-1的所有子集,然后计算每个子集的交集大小,并根据子集元素的奇偶性来决定符号。 然后,用户提供的引用中有几个代码示例,比如引用[1]中的代码,看起来像是使用位运算来生成子集,并且通过递归的gcd函数来计算最小公倍数。这可能是一个应用容斥原理的例子,用于计算在n范围内能被多个数整除的数的数量。例如,计算1到n中能被数组l中的至少一个数整除的数的个数,这可以通过容斥原理来避免重复计算。 我需要将这些思路整理成一个清晰的步骤,解释如何用C语言实现容斥原理。首先,可能需要一个函数来计算最大公约数(gcd),然后计算最小公倍数(lcm),因为交集的大小通常涉及到lcm。例如,两个数的lcm是它们的乘积除以gcd。然后,遍历所有子集,对于每个子集,计算这些数的lcm,并根据子集的大小决定加减。 然后,考虑示例代码的结构。引用[1]中的代码似乎是这样做的:输入n和m,然后输入m个数,遍历所有可能的子集(通过位掩码k从0到2^m-1),计算每个子集的lcm,并根据子集的元素个数奇偶性来调整总和s的值。例如,如果子集有奇数个元素,就减去n/lcm,否则加上。这是因为容斥原理中,奇数次交集加,偶数次减,但这里可能是相反,需要仔细看代码逻辑。 此外,用户提到的引用[2]和[3]可能涉及其他应用场景或优化方法,比如使用前缀和或其他数据结构来简化计算。不过当前问题主要是容斥原理的实现,所以可能需要集中在位运算遍历子集和lcm的计算上。 在解释代码时,需要分步骤说明,比如如何生成子集,如何计算交集大小,如何处理符号。同时,需要给出具体的例子,比如计算1到n中能被至少一个数整除的个数,并解释代码的运行流程。 可能还需要提到代码中的关键点,比如如何用位掩码k来枚举子集,如何通过位与操作检查哪些元素被包含在当前子集中,如何维护当前子集的lcm,以及如何处理溢出问题(比如r可能会超过n,这时候可以提前终止)。 最后,应用场景部分需要举几个实际的例子,比如数学问题中的计数,或者几何问题中的区域计算,引用中的几何工具应用可能相关,比如poj题目中的几何问题可能用到容斥原理来计算覆盖区域。 总结来说,回答需要包括容斥原理的C语言实现步骤,示例代码的解释,以及应用场景的举例。同时,根据用户提供的引用,特别是引用[1]中的代码,可能需要将其作为示例进行详细说明,并引用相关部分。</think>### C语言中容斥原理的实现与应用 #### 一、容斥原理基础 容斥原理用于计算多个集合的并集大小,公式为: $$|A_1 \cup A_2 \cup \dots \cup A_m| = \sum |A_i| - \sum |A_i \cap A_j| + \sum |A_i \cap A_j \cap A_k| - \dots + (-1)^{m+1}|A_1 \cap \dots \cap A_m|$$ #### 二、实现步骤 1. **位运算枚举子集** 通过二进制位掩码遍历所有子集组合(共$2^m-1$种),例如: ```c for (int k = 1; k < (1 << m); k++) ``` 2. **计算集合交集** 使用最大公约数(GCD)和最小公倍数(LCM)处理交集: ```c int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } long long lcm(long long a, long long b) { return a / gcd(a, b) * b; } ``` 3. **符号控制** 根据子集元素个数的奇偶性决定加减符号: ```c if (__builtin_popcount(k) % 2) sum += n / current_lcm; else sum -= n / current_lcm; ``` #### 三、完整示例代码 以下代码实现**计算1~n中能被数组l中任意数整除的数的个数**: ```c #include <stdio.h> int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } int main() { int n, m, l[15]; while (scanf("%d%d", &n, &m) != EOF) { for (int i = 0; i < m; i++) scanf("%d", &l[i]); int result = 0; for (int k = 1; k < (1 << m); k++) { long long current_lcm = 1; int cnt = 0; for (int i = 0; i < m; i++) { if (k & (1 << i)) { current_lcm = current_lcm / gcd(current_lcm, l[i]) * l[i]; if (current_lcm > n) break; // 防止溢出 cnt++; } } if (cnt % 2) result += n / current_lcm; else result -= n / current_lcm; } printf("%d\n", result); } return 0; } ``` #### 四、关键代码解析 1. **位掩码遍历** `k`从1到$2^m-1$遍历所有非空子集,每位表示是否选中对应元素[^1]。 2. **动态计算LCM** 通过不断更新`current_lcm`处理多个数的LCM,同时防止数值溢出[^3]。 3. **奇偶性判断** `__builtin_popcount(k)`统计二进制中1的个数,决定加减符号[^2]。 #### 五、应用场景 1. **数学问题** 计算区间内满足多个条件的整数数量(如同时被2/3/5整除的数)。 2. **几何覆盖** 求多个几何图形覆盖区域的总面积(如圆、矩形的交集计算)[^4]。 3. **组合计数** 解决排列组合中的包含排除问题,如错位排列数的计算。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值