[dfs] aw1118. 分成互质组(dfs搜索顺序+dfs状态定义+最大团+好题)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[dfs] aw1118. 分成互质组(dfs搜索顺序+dfs状态定义+最大团+好题)相关的知识,希望对你有一定的参考价值。
1. 题目来源
链接:1118. 分成互质组
相似的搜索顺序:
2. 题目解析
当这个数据比较大的时候,要使用最大团算法。 其中的优化比较难,这个数据比较小,所以可以直接暴搜。
最大团思路:将 n
个数看成 n
个点,若两数不互质则将两点之间建立一条边,则问题将转化为如何将这些点尽可能分成少的组,使得组中各点没有边相连。
暴搜思路: 两种暴搜顺序,其实和 [dfs] aw843. n-皇后问题(模板题+经典) 的两种搜索顺序差不多:
- 枚举每个数,考虑每个数放到哪个组里。 不错的题解,提供了两种方式。
- 枚举每个组,考虑组内可以放那些元素。 本题采用的是这种方式枚举。
- 优化1:
- 针对当前数,有两种放入方式:
- 能放到最后一组中。
- 自己新开一组。
- 其中第二种操作:自己新开一组一定可以被执行。第一种操作需要保证新加入数是和组内已有数是互质的。
- 在此,为了优化效率,减少组的数量,可以证明,当第一种操作和第二种操作都能够被执行时,可以只执行第一种操作,而不执行第二种操作。
- 如果能执行第一种操作,那么当前数一定和组内数是互质的。
- 如果新开一组,该数也将和新开组的组内数字是互质的。
- 所以,可以将当前数执行第一种操作,不会使答案变差。那么一定存在一个最优解,在它能同时做第一个操作、第二个操作时,尽量执行第一个操作,减少 dfs 层数。
- 优化2:
- 组内的元素是排列关系,并非是组合关系。即组 1 中元素为
{1, 2, 3}
和{3, 2, 1}
是一致的,与顺序无关。 - 所以在针对每个组放置元素时,可以按照组合顺序枚举。 而不按照排列顺序枚举。
- 在组合顺序枚举时,为了保证组内元素不重复,需要
dfs
时传入枚举的起始下标,让组内元素以下标递增。 - 例如,当下标在 6 号点能够加入
a
组中,下一次搜索时,不用从头开始再次进行判断,因为前面 1~6 号点已经被判断过了。可以直接从 6+1 = 7 号点开始枚举,加快搜索速度。如,若a
组有{3 5 7}
那么排列型枚举需要枚举3! = 6
次,而组合类型枚举按照下标递增顺序,只会枚举一次, 优化了 6 倍的时间。
具体逻辑看代码和注释就行了,本题确实是到好题。
每个组在考虑完之后,它组内的元素就已经固定下来了。
即当新开一个组时,一定是当前数无法放到前面的组中了,所以才会新开一个组。
时间复杂度: O ( 指 数 级 ) O(指数级) O(指数级)
空间复杂度: O ( n 2 ) O(n^2) O(n2)
枚举每个组能放那些元素,很慢,700 ms 左右。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 15;
int n;
int p[N];
int group[N][N];
bool st[N];
int res = 1e9; // 全局记录最小值,初始化为最大值
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
// 检查以 i 为下标的数能够添加进 group 组中
bool check(int group[], int gc, int i) {
for (int j = 0; j < gc; j ++ )
if (gcd(p[group[j]], p[i]) > 1)
return false;
return true;
}
// g:已经分的组的数量,gc:当前组存了多少数字,tc:已经有多少数字被分组了,
// start:当前组分到数的下标,组合形式,防止重复情况
void dfs(int g, int gc, int tc, int start) {
if (g >= res) return ;
if (tc == n) {
res = min(res, g);
return ;
}
bool flag = true;
for (int i = start; i < n; i ++ ) {
if (!st[i] && check(group[g], gc, i)) {
st[i] = true;
group[g][gc] = i;
dfs(g, gc + 1, tc + 1, i + 1);
st[i] = false;
flag = false;
}
}
// 当前组不能插入任何数,需要新开一个组
if (flag) dfs(g + 1, 0, tc, 0);
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", p + i);
// 当前是 1 个组,当前组内元素是 0 个,总共考虑了 0 个数,从下标 0 开始考虑选取情况
// 最终会搜完所有情况,ans 会在全局记录最小组数,直接返回答案。
dfs(1, 0, 0, 0);
printf("%d\\n", res);
return 0;
}
枚举每个元素能够放到那个组中,快,40 ms
// 枚举每个数能够放到那个组里面
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 15;
int n;
int p[N];
vector<int> g[N];
bool st[N];
int res = 1e9, len; // res 全局记录最大值,len 表示分了多少组
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
bool check(int x, int u) {
for (int i = 0; i < g[u].size(); i ++ )
if (gcd(g[u][i], x) > 1)
return false;
return true;
}
// 枚举每个数放到哪个组中
void dfs(int u) {
if (u == n) {
res = min(res, len);
return ;
}
// 枚举所有组,查看 a[u] 能否放到组中
for (int i = 0; i < len; i ++ ) {
if (check(p[u], i)) {
g[i].push_back(p[u]);
dfs(u + 1);
g[i].pop_back();
}
}
// 自己新开一组
g[len ++ ].push_back(p[u]); // len 表示下一个待加入的组的编号,所以是后置 ++
dfs(u + 1);
g[ -- len].pop_back(); // 恢复现场时,需要前置 -- ,直接将其恢复到上一个状态
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", p + i);
dfs(0);
printf("%d\\n", res);
return 0;
}
以上是关于[dfs] aw1118. 分成互质组(dfs搜索顺序+dfs状态定义+最大团+好题)的主要内容,如果未能解决你的问题,请参考以下文章
[dfs] aw1116. 马走日(dfs搜索顺序+模板题)
[dfs] aw1117. 单词接龙(dfs搜索顺序+递归理解+好题)