【题解】ars[A002]画展
题目描述
现在问题来了,小 t 被抓苦力要求收拾机房,必须腾出足够大的地方来进行画展。
设机房一共有 \(n\) 行机箱,第 \(i\) 行有 \(a_i\) 个机箱,每个机箱占用一个位置。
如果一行的机箱数量大于 \(k\) 个,那么小 t 就可以把每 \(k\) 个机箱叠在一起,同样占用一个位置,如果不足 \(k\) 个,那么就不能叠在一起。为了整齐美观,要求每行 \(k\) 均相同。
现在小 t 犯了难,于是他找到你,问你机箱占用的位置最小是多少。
输入格式
第一行一个正整数 \(n\),表示行数。
接下来 \(n\) 行,第 \(i\) 行一个正整数 \(a_i\),表示第 \(i\) 行有 \(a_i\) 个机箱。
输出格式
一行一个整数,表示机箱占用位置最小是多少。
样例 #1
样例输入 #1
3
5
8
8
样例输出 #1
6
样例 #2
样例输入 #2
10
2
3
5
7
11
13
17
19
23
29
样例输出 #2
45
提示
样例 1 解释
\(k=4\) 时最优。
数据规模与约定
对于 \(20\%\) 的数据,满足 \(1\le n\le10^3\),\(1\le a_i\le 2\times10^3\)。
对于 \(40\%\) 的数据,满足 \(1\le n\le10^5\),\(1\le a_i\le 2\times 10^3\)。
对于 \(70\%\) 的数据,满足 \(1\le n\le10^5\),\(1\le a_i\le 10^5\)。
对于 \(100\%\) 的数据,满足 \(1\le n \le 10^6\),\(1\le a_i \le 10^6\)。
题解
方便起见用 \(/\) 表示整除,\(m\) 表示最大的 \(a_i\)。
要最小化的就是 \(\sum_{i=1}^{n} a_i / k + a_i \bmod k\)。
算法一
枚举 \(k\) 再暴力计算上式并更新答案,由于 \(k\) 不超过 \(m\),所以复杂度是 \(O(nm)\)。可以获得20分。
算法二
\(a_i\) 相同的可以合并在一起,然后就得到了 \(O(n + m^2)\) 的算法,结合算法一可以获得40分。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int _ = 1e6 + 5;
int n, m, a[_], ans = 1e18;
int cnt[_];
vector< int > b;
signed main() {
cin >> n;
for(int i = 1; i <= n; ++i) {
cin >> a[i], m = max(m, a[i]);
if(cnt[a[i]] == 0) b.push_back(a[i]);
cnt[a[i]]++;
}
for(int i = 1; i <= m; ++i) {
int res = 0, v;
for(int j = 0; j < b.size(); ++j)
v = b[j], res += cnt[v] * (v / i + v % i);
ans = min(ans, res);
}
cout << ans << endl;
return 0;
}
算法三
方便起见,我们换个角度,先给答案加上 \(a_i\) 的和,再最大化“节约的位置”。
由于把 \(k\) 个机箱叠在一起节约了 \(k-1\) 的位置,令 \(z_i\) 表示 \(\sum_{i=1}^n {a_i/k}\),那么就是最大化 \((k-1)z_i\)。
枚举每个 \(a_i\) 考虑其对 \(z\) 的贡献。由于 \(a_i/k\) 至多只有 \(\sqrt{a_i}\) 种不同取值,且每种取值对应的 \(k\) 是连续一段,也就是对 \(z\) 进行若干次区间加上一个值的操作,每次可以做到 \(O(1)\)。
最终复杂度是 \(O(n \sqrt{m} + m)\)。可以获得70分。
算法四
算法三其实把问题想复杂了。
我们还是枚举 \(1\) 到 \(m\),考虑如何快速计算答案,同样的令 \(z_i\) 表示 \(\sum_{i=1}^n {a_i/k}\),那么就是最大化 \((k-1)z_i\)。
我们对每个 \(k\) 直接计算 \(z_k\) 就好了:枚举 \(a_i/k\) 的值 \(t\),查询满足 \(tk \le a_i < (t+1)k\) 的\(i\)的个数,然后给 \(z_k\) 加上值和个数的积。注意 \(a_i/k\) 的值只需枚举到 \(m/k\)。
我们统计 \(c_k\) 表示 \(a_i=k\) 的 \(i\) 的个数,记录 \(c\) 的前缀和以便查询区间和。
一个著名的性质是:
所以我们就得到了一个
\(O(m/1 + m/2 + m/3 + \dots + m/m)=O(m \log m)\)的算法。可以获得100分。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int _ = 1e6 + 5;
int n, m, a[_], s[_];
int num, del;
int solve(int k) {
int res = 0;
for(int i = 1; i <= m / k; ++i)
res += i * (s[min(k * (i + 1) - 1, m)] - s[k * i - 1]);
return res * (k - 1);
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i], s[a[i]]++, m = max(m, a[i]), num += a[i];
for(int i = 1; i <= m; ++i)
s[i] += s[i - 1];
for(int i = 2; i <= m; ++i)
del = max(del, solve(i));
cout << num - del << endl;
return 0;
}