【UOJ 21】缩进优化

【题目】

传送门

题目描述:

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 时请用 %lldC++ 可以直接使用 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 n1000 a i ≤ 1000 a_i≤1000 ai1000
对于 40 % 40\% 40% 的数据, n ≤ 100000 n≤100000 n100000 a i ≤ 1000 a_i≤1000 ai1000
对于 70 % 70\% 70% 的数据, n ≤ 100000 n≤100000 n100000 a i ≤ 100000 a_i≤100000 ai100000
对于 100 % 100\% 100% 的数据, n ≤ 1000000 n≤1000000 n1000000 a i ≤ 1000000 a_i≤1000000 ai1000000

时间限制: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(nmaxn)

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 x1 的代码。若令 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 (x1)fi

对于 [ k x kx kx , , , ( k + 1 ) x − 1 (k+1)x-1 (k+1)x1 ] 内的所有数,它们整除 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 sjsi 就是 [ 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)x1 ] 内的数的个数乘 k k k 作为当前贡献统计答案。

最后取个最大值,再用总和相减即可。

由于我们是枚举倍数,那时间复杂度就是一个调和级数,为 O ( x ⋅ log ⁡ 2 x ) O(x\cdot \log_2x) O(xlog2x)


【代码】

#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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值