题目地址:
https://www.acwing.com/problem/content/1120/
给定 n n n个正整数,将它们分组,使得每组中任意两个数互质。至少要分成多少个组?
输入格式:
第一行是一个正整数 n n n。第二行是 n n n个不大于 10000 10000 10000的正整数。
输出格式:
一个正整数,即最少需要的组数。
数据范围:
1 ≤ n ≤ 10 1≤n≤10 1≤n≤10
我们可以依次枚举第 i i i个组可以放哪些数,每次枚举完第 i i i个组放哪些数之后,再枚举第 i + 1 i+1 i+1个组可以放哪些数,以此类推。在枚举第 i i i个组放哪些数的时候,因为这个组里的数是按照何种顺序放进去的是没有区别的,所以我们可以人为地按次序枚举这 n n n个正整数,即按下标从小到大枚举这些数。当枚举第 i i i组放哪个数的时候,如果当前备选的数里都不能放了,那显然要开个新组,即第 i + 1 i+1 i+1组,然后继续从第 i + 1 i+1 i+1组开始枚举放哪些数;如果能放,那么此时仍然有两种选择,要么放进第 i i i组,要么新开一组然后放进第 i + 1 i+1 i+1之后的某个组。我们可以证明,对于第二种选择的任意合法的解,都有一个不会更差的解与之对应:如果此时是将这个数放在了新开的第 i + 1 i+1 i+1组后面的某个组,那么形成的新的解中,可以将这个数挪到第 i i i组里面去,这时仍然是个合法的解,并且组数不会变多(而且还有可能变得更少)。所以这就造成了,最优解一定在第一种方案里,所以我们不必枚举第二种方案,只需“能放就放”即可(严格证明可以用构造法,如果某个解是第二种方案得到的,即新开组得到的,那么可以先将每个组里的数按下标从小到大排序,接着再将这些组按照组内第一个元素的下标从小到大排序,然后进行挪动操作,先将能加到第 1 1 1个组里的数都挪到第 1 1 1个组里,并添加到最后面,也就是说只挪那些下标比第 1 1 1个组里最后一个数下标更大的数,然后再看第 2 2 2个组,将能加到第 2 2 2个组里的数都挪到第 2 2 2个组里,并添加到后面,以此类推。该过程一定可以在有限步内停止。停止所得的解一定是个合法解,并且不会更差,并且这个解是算法能够搜到的那个解。所以就证明了算法是可以搜到最优解的)。代码如下:
#include <iostream>
using namespace std;
const int N = 11;
int n;
int a[N];
int g[N][N];
int res = N;
bool st[N];
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
// 看一下第u组是否能加入a[idx]这个数
bool check(int u, int gc, int idx) {
for (int i = 0; i < gc; i++)
if (gcd(a[g[u][i]], a[idx]) > 1) return false;
return true;
}
// u是正在枚举第几组,gc是正在枚举的这一组里已经有了多少个数,
// tc是已经枚举了多少个数了,start表示从a[start]开始枚举
void dfs(int u, int gc, int tc, int start) {
// 枚举到了不优于res的答案,则直接返回,不用继续枚举
if (u >= res) return;
// 枚举到一个更优解,则记录之并返回
if (tc == n) {
res = u;
return;
}
bool added = false;
// 枚举第u组还能添加哪个数
for (int i = start; i < n; i++) {
// 如果a[i]没被选过,并且确实能加进第u组里去,则枚举这种情况
if (!st[i] && check(u, gc, i)) {
st[i] = true;
g[u][gc] = i;
dfs(u, gc + 1, tc + 1, i + 1);
st[i] = false;
added = true;
}
}
// 只要当前第u组还能加进一个数,那么就不枚举不加这个数的方案
if (!added) dfs(u + 1, 0, tc, 0);
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
dfs(1, 0, 0, 0);
cout << res << endl;
return 0;
}
时间复杂度指数级,空间 O ( n ) O(n) O(n)。