【题目】
题目描述:
小 O 是一个热爱短代码的选手。在缩代码方面,他是一位身经百战的老手。世界各地的 OJ 上,很多题的最短解答排行榜都有他的身影。这令他感到十分愉悦。
最近,他突然发现,很多时候自己的程序明明看起来比别人的更短,实际代码量却更长。这令他感到很费解。经过一番研究,原来是因为他每一行的缩进都全是由空格组成的,大量的空格让代码量随之增大。
现在设小 O 有一份 n n n 行的代码,第 i i i 行有 a i a_i ai 个空格作为缩进。
为解决这一问题,小 O 要给自己文本编辑器设定一个正整数的默认 TAB 宽度 x x x,然后对于每一行,编辑器从头至尾不断把连续 x x x 个空格替换成一个 TAB,直到剩余空格数不足 x x x 个。
最终缩进所占代码量为空格数与 TAB 数的和。请你帮小 O 选择一个合适的 x x x,使得缩进所占代码量最小。
输入格式:
第一行一个正整数 n n n,表示代码行数。
接下来 n n n 行,第 i i i 行一个正整数 a i a_i ai,为初始时第 i i i 行缩进的空格个数。
输出格式:
一行一个整数,表示缩进所占代码量最小是多少。
C/C++ 输入输出 long long 时请用 %lld。C++ 可以直接使用 cin/cout 输入输出。
样例数据:
【样例 1 1 1】
输入
3
5
8
8
输出
6
解释
令默认 TAB 宽度为
4
4
4 即可。
【样例 2 2 2】
输入
10
2
3
5
7
11
13
17
19
23
29
输出
45
数据规模与约定:
对于
20
%
20\%
20% 的数据,
n
≤
1000
n≤1000
n≤1000,
a
i
≤
1000
a_i≤1000
ai≤1000
对于
40
%
40\%
40% 的数据,
n
≤
100000
n≤100000
n≤100000,
a
i
≤
1000
a_i≤1000
ai≤1000
对于
70
%
70\%
70% 的数据,
n
≤
100000
n≤100000
n≤100000,
a
i
≤
100000
a_i≤100000
ai≤100000
对于
100
%
100\%
100% 的数据,
n
≤
1000000
n≤1000000
n≤1000000,
a
i
≤
1000000
a_i≤1000000
ai≤1000000
时间限制:1s
空间限制:256MB
【分析】
20    p t s 20\;pts 20pts:
为了方便,我们用 / / / 代表整除, % \% % 代表取模, m a x n maxn maxn 代表最大的 a i a_i ai。
不难发现,我们需要最小化 ∑ i = 1 n ( a i / x + a i % x ) \sum_{i=1}^n(a_i/x+a_i\%x) ∑i=1n(ai/x+ai%x)。
直接枚举 [ 1 1 1 , , , m a x n maxn maxn ] 的所有数,暴力计算即可。
时间复杂度 O ( n ⋅ m a x n ) O(n\cdot maxn) O(n⋅maxn)
40    p t s 40\;pts 40pts:
a i a_i ai 相同的可以合并起来,剩下的就和上面的算法一样了。
100    p t s 100\;pts 100pts:
我们还是枚举 [ 1 1 1 , , , m a x n maxn maxn ] 的所有数,考虑如何快速计算答案。
考虑将问题换一个角度,我们先计算出所有空格的长度总和,再减去最大的 “ “ “节约的代码 ” ” ”。
假设我们用 x x x 个空格替换成一个 TAB,它会节约了 x − 1 x-1 x−1 的代码。若令 f i f_i fi 表示 ∑ i = 1 n a i / x \sum_{i=1}^n a_i/x ∑i=1nai/x,那么就是最大化 ( x − 1 ) ⋅ f i (x-1)\cdot f_i (x−1)⋅fi。
对于 [ k x kx kx , , , ( k + 1 ) x − 1 (k+1)x-1 (k+1)x−1 ] 内的所有数,它们整除 x x x 得到的值是一样的,都是 k k k。
我们用一个桶, s i s_i si 表示 i i i 出现的次数,并统计出 s s s 的前缀和。那么用 s j − s i s_j-s_i sj−si 就是 [ i + 1 i+1 i+1 , , , j j j ] 这段区间所有数的个数。
因此我们只用枚举 x x x 的所有倍数,把 [ k x kx kx , , , ( k + 1 ) x − 1 (k+1)x-1 (k+1)x−1 ] 内的数的个数乘 k k k 作为当前贡献统计答案。
最后取个最大值,再用总和相减即可。
由于我们是枚举倍数,那时间复杂度就是一个调和级数,为 O ( x ⋅ log 2 x ) O(x\cdot \log_2x) O(x⋅log2x)
【代码】
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1000005
using namespace std;
int s[N];
int main()
{
int n,i,Max=0;
scanf("%d",&n);
long long sum=0;
for(i=1;i<=n;++i)
{
int x;
scanf("%d",&x);
Max=max(Max,x);
s[x]++,sum+=x;
}
long long ans=sum;
for(i=2;i<=Max;++i) s[i]+=s[i-1];
for(i=2;i<=Max;++i)
{
int l,r,p=0;
long long now=0;
for(l=1,r=i-1;l<=r;)
{
now+=1ll*p*(s[r]-s[l-1]);
p++,l=r+1,r=min(Max,r+i);
}
now*=(i-1);
ans=min(ans,sum-now);
}
printf("%lld",ans);
return 0;
}