链接:
题目大意:
给定一个数组 \(a\),找到一个两两互质的数组 \(b\) 使得 \(\sum\limits_{i=1}^{n}|a_i-b_i|\) 最小。
正文:
我们可以从 \(a_i\leq 30\) 想到这道题可能与状压沾边。设 \(f_{i,J}\) 表示前 \(i\) 个数选了集合 \(J\) 中的质数作为 \(b_i\) 的质因数。其中集合 \(J\) 在实现中就是那么有:
\[f_{i,J}=\min_{K\subseteq J,k=|J-K|}\{f_{i-1,K}+|a_i-k|\} \]但是还有一个问题:我们不知道 \(b_i\) 的范围。
这个问题也好解决。还是 \(a_i\leq 30\),因为 \(b_i\) 最小是 \(1\),那么 \(|a_i-b_i|\) 的上界是 \(30-1=29\)。也就是说 \(b_i\) 不超过 \(30+29=59\),又因为差值如果是 \(29\) 的话,\(b_i=1\) 会更优,毕竟 \(1\) 可以重复选择。所以实际上 \(b_i\) 不超过 \(58\)。
以上是我的思考过程,仅供参考。如果要看更严谨的证明,可以移步题解区看 @周子衡 爷的题解。
代码:
我代码里 \(b_i\) 的上界还是 \(59\) 的,可以改成 \(58\)。
int pri[20] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59};
void Output(int x, int y)
{
if (!x) return;
Output(x - 1, y ^ has[ans[x][y]]);
printf ("%d ", ans[x][y]);
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
scanf ("%d", &n);
for (int i = 1; i <= n; ++i)
scanf ("%d", &a[i]);
for (int i = 1; i <= 59; i++)
for (int j = 0; j < 17; j++)
if(!(i % pri[j])) has[i] |= 1 << j;
for (int i = 1; i <= n; i++)
{
f[i][0] = 1e9;
for (int j = 0; j < (1 << 17); f[i][++j] = 1e9)
for (int k = 1, tmp = 0; k <= 59; k++)
{
if ((has[k] | j) != j) continue;
tmp = abs(a[i] - k) + f[i - 1][j ^ has[k]];
if (tmp < f[i][j]) f[i][j] = tmp, ans[i][j] = k;
}
}
Output(n, (1 << 17) - 1);
return 0;
}