【题解】CF2039D Shohag Loves GCD
思路(贪心 + 唯一分解定理)
这个题其实只需要考虑一件事:记答案数组为 \(a\),对于两个不同下标 \(i\) 和 \(j\),当 \(\gcd(i,j) = \min(i,j)\) 时,我们只需要让 \(a_{\max(i,j)} < a_{\min(i,j)}\) 即可。
证明:任意两个数 \(x,y\),一定有 \(\gcd(x,y) \leq \min(x,y)\)。
第一种情况,如果 \(\gcd(i,j) = \min(i,j)\),显而易见按上述构造是满足题意的,因为 \(a_{\gcd(i,j)} = a_{\min(i,j)} = \max(a_i,a_j) \neq \gcd(a_i,a_j)\)。
第二种情况,如果 \(\gcd(i,j) \neq \min(i,j)\),那么 $\gcd(i,j) < \min(i,j) $,记 \(c = \gcd(i,j)\),则 \(\gcd(i,c) = c\) 并且 \(\gcd(j,c) = c\),此时问题转化为第一种情况。
证毕。
答案要最大字典序,那首先就把原集合元素从大到小排列来贪心求解。
对 \(1\) 到 \(n\) 中的每一个答案数组下标唯一分解一下,记当前下标为 \(i\),分解出来的素数为 \(k\)。
因为除掉的数越少,前置下标包含的因子一定越多,求得的答案肯定越能满足前面所有的下标。
所以只需要对每一个答案数组下标用除掉一个素数的前置下标来转移就行了。
我们用 \(ans_i\) 来表示答案数组下表为 \(i\) 的位置的对应的集合中的元素下标,则有转移方程 \(ans_i = \max\{ans_{i / k} + 1\}\)。
而这个过程可以直接预处理,因为我们只需要得到每一个答案数组的下标对应的一个集合内部的下标。
时间复杂度:\(O(n\sqrt{n})\)。
AC CODE
#include <bits/stdc++.h>
#define int long long
#define inf 2e18
#define ull unsigned long long
#define ls o << 1
#define rs o << 1 | 1
using namespace std;
const int N = 1e5 + 9;
int a[N];
int ans[N];
int n, m;
bool check()
{
for(int i = 1;i <= n;i ++)
if(ans[i] > m)return false;//只要下标超出了集合的大小,就无解
return true;
}
void solve()
{
cin >> n >> m;
for(int i = 1;i <= m;i ++)cin >> a[i];
reverse(a + 1, a + m + 1);//将集合元素从大到小排列
if(check())
{
for(int i = 1;i <= n;i ++)cout << a[ans[i]] << " \n"[i == n];
}
else cout << -1 << '\n';
}
void init()//预处理每个答案数组的下标对应的集合下标
{
ans[1] = 1;
for(int i = 2;i < N;i ++)
{
int tmp = i;
for(int j = 2;j * j <= tmp;j ++)
{
if(tmp % j == 0)
{
ans[i] = max(ans[i], ans[i / j] + 1);
while(tmp % j == 0)tmp /= j;
}
}
if(tmp > 1)ans[i] = max(ans[i], ans[i / tmp] + 1);
}
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
init();
int t = 1;cin >> t;
while(t --)solve();
return 0;
}
作者: 天天超方的
出处: https://www.cnblogs.com/TianTianChaoFangDe
关于作者:ACMer,算法竞赛爱好者
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显醒目位置给出, 原文链接 如有问题, 可邮件(1005333612@qq.com)咨询.

浙公网安备 33010602011771号