题目描述
难度分:1500
输入n(1≤n≤1000)。
A和B玩一个猜数字游戏。A心中想一个在[1,n]中的数字x。B去猜x是多少。
猜测方法:B一次性询问所有要询问的数,A对于每个询问的数y,回答x能否被y整除。
B至少要询问多少个数,才能保证可以猜出数字x?输出询问的数字个数,以及具体询问了哪些数。
注意:B知道n是多少。
输入样例1
4
输出样例1
3
2 4 3
输入样例2
6
输出样例2
4
2 4 3 5
算法
递推
挺有意思的一道题,在做的时候不太确定解法对不对,如果是比赛的话最好是自己多对拍几个n,这题光凭样例还是挺容易WA
的。
- 当n=1时,直接输出0。
- 当n=2时,只需要询问能不能被2整除。
- 当n=3时,只需要询问能不能被2整除,能不能被3整除,两个数足矣。
- 否则先将2和3加入答案集合ans,然后从4开始递推。对于某个数num,枚举它在区间[2,n)里的约数d,只要d<numd且两者都能被ans里面的数表示出来(可以用一个dp数组标记所有能被表示出来的数),并且d不是numd的因子,那当前的num就不用加入ans了。否则就说明当前掌握的所有因子不能唯一确定出num,需要加入num这个数,才能把num猜出来。
递推完n后,得到的ans数组就是我们要求的答案,相继输出ans的大小和其中所有的元素即可。
复杂度分析
时间复杂度
遍历num∈[4,n]时间复杂度为O(n)。对于每个num,需要遍历它的约数,从2枚举到num−1也是O(n)的时间复杂度。因此,整个算法的时间复杂度为O(n2)。
空间复杂度
空间消耗主要在于标记数组dp,它是线性的,额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1001;
int n;
bool dp[N];
int main() {
scanf("%d", &n);
if(n == 1) {
puts("0");
}else if(n == 2) {
puts("1\n2");
}else if(n == 3) {
puts("2\n2 3");
}else {
memset(dp, 0, sizeof dp);
dp[1] = dp[2] = dp[3] = true;
vector<int> ans{2, 3};
for(int num = 4; num <= n; num++) {
for(int d = 2; d < num; d++) {
if(num % d == 0 && d < num / d && num / d % d != 0) {
dp[num] = dp[num] || (dp[d] && dp[num / d]);
}
}
if(!dp[num]) {
dp[num] = true;
ans.push_back(num);
}
}
printf("%d\n", (int)ans.size());
for(int factor: ans) {
printf("%d ", factor);
}
}
return 0;
}