分成互质组

分成互质组

给定 $n$ 个正整数,将它们分组,使得每组中任意两个数互质。

至少要分成多少个组?

输入格式

第一行是一个正整数 $n$。

第二行是 $n$ 个不大于 $10000$ 的正整数。

输出格式

一个正整数,即最少需要的组数。

数据范围

$1 \leq n \leq 10$

输入样例:

6
14 20 33 117 143 175

输出样例:

3

 

解题思路

  记录一下这道题,对搜索顺序不是很理解,以及搜索时如何消除冗余(排列变组合)。

  首先给出比较容易想到的搜索顺序,就是从前往后枚举每一个数,对于当前数,枚举一下这个数可以放到已有的哪个组中,如果某个组可以放入这个数,那么就把这个数放入这个组中,然后接着枚举下一个数。当然这个数还可以放入到一个新的空组中,这种情况也要枚举。

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 15;
 5 
 6 int n;
 7 int a[N];
 8 vector<int> g[N];
 9 int ans = N;
10 
11 int gcd(int a, int b) {
12     return b ? gcd(b, a % b) : a;
13 }
14 
15 bool check(int x, int cnt) {
16     for (auto &it : g[cnt]) {
17         if (gcd(x, it) != 1) return false;
18     }
19     return true;
20 }
21 
22 void dfs(int u, int cnt) {
23     if (cnt >= ans) return;
24     if (u == n) {
25         ans = cnt;
26         return;
27     }
28     
29     // 枚举放入哪个组
30     for (int i = 0; i < cnt; i++) {
31         if (check(a[u], i)) {
32             g[i].push_back(a[u]);
33             dfs(u + 1, cnt);
34             g[i].pop_back();
35         }
36     }
37     
38     // 放入新的组的情况
39     g[cnt].push_back(a[u]);
40     dfs(u + 1, cnt + 1);
41     g[cnt].pop_back();
42 }
43 
44 int main() {
45     cin >> n;
46     for (int i = 0; i < n; i++) {
47         cin >> a[i];
48     }
49     
50     dfs(0, 0);
51     cout << ans;
52     
53     return 0;
54 }

  第二种搜索顺序是枚举每一个组可以放哪些数。比如对于第一组,先枚举所有的数,看哪些数可以放入第一组中。然后是第二组,从剩余没被选的数中看一下哪些数可以放入第二组中,以此类推直到所有的数都已经被选择。说实话对这种搜索顺序总感觉不是很理解,有点怪怪的。

  然后是判断搜索的过程中是否有冗余。当考虑哪些数要放入这组的时候其实是个组合问题,比如说放入$\{1,2,3\}$(这个是数的下标,下同),或者是$\{1,3,2\}$,或者是$\{3,2,1\}$,最终的结果是一样的,因为组内顺序不会影响结果。而要实现这种组合的枚举方式只要人为定义一种顺序就好了,即按照下标从小到大往组内加数,实现的方式是搜索时传入一个参数$u$,表示从$u$这个下标开始添加数。

  刚才是组内的冗余,然后是每个组之间的冗余。可以知道当每一个组都已经添加完数了,那么各个组间的顺序是怎么样都不会影响最终结果,因为这是个组合问题。比如说当前有两个组$\{\{1,2\},\{3,4\}\}$,如果变换一下顺序有$\{\{3,4\},\{1,2\}\}$,可以发现各个组间的顺序是不影响结果的。为了避免搜索排列的情况,在某个组搜索完添加哪些数时,可以判断一下这个组是不是为空,如果是空的那么就直接回溯就好了。这是因为这个组是一个新组,必然会放入还没被选的数中第一个数,因此当搜索完所有的情况后,如果继续往这个组中添加没被选的下一个数,就会产生排列的情况,后面某个组会放入没被选的数中第一个数,就会有冗余。

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 15;
 5 
 6 int n;
 7 int a[N];
 8 vector<int> g[N];
 9 int ans = N;
10 
11 int gcd(int a, int b) {
12     return b ? gcd(b, a % b) : a;
13 }
14 
15 bool check(int x, int cnt) {
16     for (auto &it : g[cnt]) {
17         if (gcd(x, it) != 1) return false;
18     }
19     return true;
20 }
21 
22 void dfs(int u, int cnt, int st) {
23     if (cnt + 1 >= ans) return;
24     if (st == (1 << n) - 1) {   // 所有的数都被选了
25         ans = cnt + 1;
26         return;
27     }
28     
29     // 当前是第cnt个组
30     bool flag = false;  // 用来标记这个组在本次枚举中有没有加入数
31     for (int i = u; i < n; i++) {   // 从u开始枚举,避免组内冗余
32         if (st >> i & 1) continue;  // 第i个数被选了
33         if (check(a[i], cnt)) {
34             g[cnt].push_back(a[i]);
35             dfs(i + 1, cnt, st | 1 << i);
36             g[cnt].pop_back();
37             flag = true;
38             if (g[cnt].empty()) return; // 这个组是新组,已经枚举完组合的情况了,回溯,避免组间冗余
39         }
40     }
41     if (!flag) dfs(0, cnt + 1, st); // 本次枚举已经没有可以加的数了,开新组
42 }
43 
44 int main() {
45     cin >> n;
46     for (int i = 0; i < n; i++) {
47         cin >> a[i];
48     }
49     
50     dfs(0, 0, 0);
51     cout << ans;
52     
53     return 0;
54 }

 

参考资料

  AcWing 1118. 分成互质组(算法提高课):https://www.acwing.com/video/477/

posted @ 2022-09-21 21:03  onlyblues  阅读(336)  评论(0编辑  收藏  举报
Web Analytics