P9345 夕阳西下几时回 题解
Untitled10032 · · 题解
题意
构造一个由
思路
构造数列:由一个给定数开始,第
这样的数列第
那么我们可以将许多这样的数列拼起来组成答案。
具体操作是:
- 先把
1 放进排列里。 - 从
2 开始,生成以2 开头的,下一项为上一项的两倍的数列,直至下一个数将要大于n ,将其放入答案排列中,对于数列的每一个数,标记其已被使用。 - 从小到大寻找下一个未被使用的数,之后生成以它开头的相应的数列,放入答案排列,将其中的元素标记已使用。
- 循环进行寻找、生成、标记的操作,直到序列中有
k 个互不相同的最大公因数或当前考虑的数已经为n 。 - 如果成功构造了满足要求的数列,那么把得到的答案数列输出,之后从小到大枚举
1 \sim n ,把所有未使用的数按顺序输出。 - 如果未构造出满足要求的数列,那么无解。
证明
为什么这样处理,最后的那些未被使用的数从小到大输出不会形成新的最大公约数?
考虑“从小到大寻找下一个未被使用的数”的过程,易得找到的一定是下一个奇数,所有偶数都已经被包含在之前的数列中,而未被当做(后一项是前一项两倍的)数列开始元素的奇数一定没有被使用过。如果生成一半时,因已经满足了最大公因数个数要求而停止,那么会剩下一些未使用的偶数,而这些偶数一定不会小于最后的数列里的那个奇数,那么这些偶数的前和后必定有与之相邻的奇数,相邻数都互质,相邻奇数都互质,所以不会产生新的最大公约数。
为什么这样可以构造出最优解?如何证明没有更优的做法?
首先,给定
那么为什么这样的做法可以把
代码
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 3e5 + 5;
bool a[N]; //标记某数是否使用过的数组
vector<int> ans; //答案序列
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t;
cin >> t;
while (t--) {
ans.clear();
int n, k;
cin >> n >> k;
int cnt = 0;
memset(a, 0, (n + 1) * sizeof(bool));
k--; //先把 1 作为最大公约数的情况减去
ans.push_back(1); //把 1 放在序列开头
a[1] = true;
for (int i = 2; i <= n; i++) {
if (cnt == k)
break;
if (!a[i]) {
a[i] = true;
ans.push_back(i);
for (int j = (i << 1); j <= n; j <<= 1) {
a[j] = true;
ans.push_back(j);
if (++cnt == k)
break;
}
}
}
if (cnt < k) {
cout << "No\n";
continue;
} else
cout << "Yes\n";
for (int i : ans)
cout << i << ' ';
for (int i = 2; i <= n; i++)
if (!a[i])
cout << i << ' ';
cout << '\n';
}
return 0;
}