LGTB 与序列
LGTB 与序列(数论 \(\star\star \))
- \(LGTB\) 有一个长度为 \(N\) 的序列 \(A\),现在他想构造一个新的长度为 \(N\) 的序列 \(B\),使得 \(B\) 中的任意两个数都互质。并且他要使\[\sum_{1\le i\le N}|A_i-B_i| \]最小,请输出最小值。
Input
- 第一行包含一个数 \(N\) 代表序列初始长度。
- 接下来一行包含 \(N\) 个数 \(A_1, A_2, …, A_N\),代表序列 \(A\)。
Output
- 输出包含一行,代表最小值。
Sample Input
5
1 6 4 2 8
Sample Output
3
Hint
- 样例解释:\(B=\{1,5,3,2,7\}\) ,\(1\) 与任何数都互质。
- 对于 \(40\%\) 的数据, \(1 ≤ N ≤ 10\)。
- 对于 \(100\%\) 的数据, \(1 ≤ N ≤ 100, 1 ≤ A_i≤30\)。
- 来源:
分析
- \(A_i\le 30 \Rightarrow B_i \le 58\) ,因为 \(B_i\) 变成更大的数还不如直接取 \(1\),而且 \(58\) 之内只有 \(16\) 个素数。
- 如果原数列中 \(A_i>A_j\),那么最优答案一定是 \(B_i>B_j\) 的情况。所以最多只有 \(16\) 个最大的数变成素数,其他的数都会变成 \(1\) 。
- 所以直接对于前 \(16\) 个数 \(dp, dp[i][j]\) 代表到第 \(i\) 个数,哪些素因子被用过了,花费最少为多少。枚举一个数来转移即可
Code
#include <bits/stdc++.h>
const int maxn=105,maxp=17,Inf=0x3f3f3f3f;
int prime[] = {0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53}; // 16 primes
int n,a[maxn];
int dp[maxp][1<<maxp];
int status[maxn];
void Init(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",a+i);
std::sort(a+1,a+n+1,std::greater<int>());
memset(status,0,sizeof(status));
for(int i=1;i<=58;i++)
for(int j=1;j<=16;j++)
if(i<prime[j]) break;
else if(i%prime[j]==0)//分解质因数
status[i]|=(1<<j-1);//i的质因数情况
}
void Solve(){
memset(dp,0x3f,sizeof(dp));
dp[0][0]=0;
for(int i=1;i<=std::min(n,16);i++)//最多处理最大的16个
for(int S=0;S<(1<<16);S++) //枚举上一个状态
for(int num=1;num<=58;num++)//枚举每一个可能的B[i]
if(!(status[num]&S)){//状态S中没有num的质因子
int s=S|status[num];//num的质因数都并入下一个状态
dp[i][s]=std::min(dp[i][s],dp[i-1][S]+abs(a[i]-num));
}
int ans=Inf;
for(int S=0;S<(1<<16);S++)//枚举左后一行的所有状态
ans=std::min(ans,dp[std::min(n,16)][S]);
if(n>16)//剩下的均选1
for(int i=17;i<=n;i++)
ans+=abs(a[i]-1);
printf("%d\n",ans);
}
int main(){
Init();
Solve();
return 0;
}
hzoi