Loading

【题解】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\) 的前缀和以便查询区间和。

一个著名的性质是:

\[\sum_{k = 1}^{n}{\frac{1}{k}} = O(\log n) \]

所以我们就得到了一个
\(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;
}
posted @ 2023-05-25 22:30  AgrumeStly  阅读(23)  评论(0编辑  收藏  举报