题目链接
题目翻译:
给你n个正整数a1,a2,…,an。你需要使用这些数组成一个数列b1,b2,…,bn,每个数只能用一次,且必须所有的数都被用到。另外ci表示GCD(b1,…,bi),即前i个数的最大公约数。组成的数列b要使得数列c1,c2,…,cn字典序最大。
当且仅当数列a和b满足下列任一条件时,数列a的字典序小于数列b:
- a是b的前缀,但是a≠b。
- 依次比较两个数列,两个数列第一次不相同是在下标为i的地方,且ai小于bi。
解题思路:
首先我们可以确定,第一个数一定是最大的数(因为其最大公约数就是其本身),所以我们把最大的数排到第一位。然后,在已经确定前k-1个数的情况下,我们要怎么选择第k个数?当然是找到一个数使得前k个数的最大公约数最大的。问题是怎么找更方便,快速。比如我们要怎么得出4,6,8的最大公约数,我们可以先求出4和6的最大公约数2,再求出2和8的最大公约数2,最后得出的结果就是2。即求前k个数的最大公约数,也就是求前k-1个数的最大公约数和第k个数的最大公约数。所以在确定前k-1个数,选择第k个数的时候,我们可以遍历剩下的数,找出一个数,使得前k-1个数的最大公约数和第k个数的公约数最大。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 1010;
int t,n,a[N];
vector<int>v;
void solve(){
v.clear();
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
sort(a,a+n);
v.push_back(a[n-1]);
int sum=n-1,c=a[n-1],ind=n-1;
a[n-1]=0;
while(sum--){
int cur_max = 0;
for(int i=0;i<n;i++){
if(a[i]&&__gcd(c,a[i])>=cur_max){
cur_max=__gcd(c,a[i]);
ind = i;
}
}
v.push_back(a[ind]);
a[ind]=0;
c = cur_max;
}
for(int i=0;i<n;i++){
cout<<v[i]<<" ";
}
cout<<endl;
return ;
}
int main(){
// freopen("1.txt","r",stdin);
cin>>t;
while(t--){
solve();
}
return 0;
}
我的思路:
我写的就比较复杂,主要是没想到前k-1个数的最大公约数和第k个数的最大公约数就是前k个数的最大公约数这一点。
先求出最大数的所有因数,存入v,然后从大到小依次遍历所有因数,对于每个因数v[i],找到能整除v[i]的所有a[j],每找到一个数a[j],就去除v中不是a[j]因数的数。
麻烦是麻烦,好歹对了。
我的代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 1010;
int t,n,a[N],b[N];
vector<int>v,v2;
int main() {
// freopen("1.txt","r",stdin);
cin>>t;
while(t--) {
cin>>n;
for(int i=0; i<n; i++) cin>>a[i];
sort(a,a+n);
reverse(a,a+n);
v.clear();
for(int i=a[0]; i>=1; i--) {
if(a[0]%i==0) v.push_back(i);
}
memset(b,0,sizeof b);
b[0]=1;
int sum=n-1,index=1,index2=0;
cout<<a[0]<<" ";
while(sum>0) {
if(b[index]==1) {
index++;
if(index==n) {
index=1;
index2++;
if(index2==v.size()) {
for(int i=1; i<n; i++) {
if(b[i]==0) cout<<a[i]<<" ";
}
sum=0;
break;
}
}
continue;
}
if(a[index]%v[index2]==0) {
cout<<a[index]<<" ";
b[index]=1;
sum--;
v2.clear();
for(int i=index2;i<v.size();i++){
if(a[index]%v[i]==0) v2.push_back(v[i]);
}
v=v2;
index2=0;
}
index++;
if(index==n) {
index=1;
index2++;
if(index2==v.size()) {
for(int i=1; i<n; i++) {
if(b[i]==0) cout<<a[i]<<" ";
}
sum=0;
break;
}
}
}
cout<<endl;
}
return 0;
}
总结:
这题错了两次,一次是因为没有考虑到细节,没有去更新v里的数。如果不更新v,找到的数虽然是最大值的因数v[i]的倍数,但v[i]可能不是其他数的因数。举个例子,a=[72,18,16,3],72的因数为[72,36,24,18,12,9,8,6,3,2,1],前两个数是72,18,选择第三个数的时候,按原本的逻辑就会选16,16是8的倍数,而3只是3的倍数,这样最后的答案就会是[72,18,16,3]。但是是错的,因为72,18,16的最大公约数是2,而72,18,3的最大公约数是3,所以应该先选3。这是因为我只考虑了最大值,忽略了其他已经被选走的数。所以在选中18之后,我们应该将v更新为[18,9,6,3,2,1],再进行后面的操作。第二次就是粗心,忘记v2.clear();