【题解】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;
}

posted @ 2024-12-10 17:21  天天超方的  阅读(46)  评论(0)    收藏  举报