CodeTON Round 9 D.Shohag Loves GCD

天天超方的發表於2024-12-10

思路(貪心 + 唯一分解定理)

這個題其實只需要考慮一件事:記答案陣列為 \(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;
}

相關文章