UOJ #22 缩进优化

298 阅读3分钟

题目

描述

小O是一个热爱短代码的选手。在缩代码方面,他是一位身经百战的老手。世界各地的OJ上,很多题的最短解答排行榜都有他的身影。这令他感到十分愉悦。

最近,他突然发现,很多时候自己的程序明明看起来比别人的更短,实际代码量却更长。这令他感到很费解。经过一番研究,原来是因为他每一行的缩进都全是由空格组成的,大量的空格让代码量随之增大。

现在设小O有一份 n 行的代码,第 i 行有 ai 个空格作为缩进。

为解决这一问题,小O要给自己文本编辑器设定一个正整数的默认TAB宽度 x,然后对于每一行,编辑器从头至尾不断把连续 x 个空格替换成一个TAB,直到剩余空格数不足 x 个。

最终缩进所占代码量为空格数与TAB数的和。请你帮小O选择一个合适的 x,使得缩进所占代码量最小。

输入格式

第一行一个正整数 n,表示代码行数。

接下来 n 行,第 i 行一个正整数 ai,为初始时第 i 行缩进的空格个数。

输出格式

一行一个整数,表示缩进所占代码量最小是多少。

C/C++ 输入输出 long long 时请用 %lld。C++ 可以直接使用 cin/cout 输入输出。

限制与约定

n≤10^6 a_i≤10^6

时间限制:1s

空间限制:256MB

AC通道

这货可能没用过自动缩进吧

题解(参考官方题解)

自己思考过程

第一步暴力枚举,发现没有啥规律,不满足二分性

第二步尝试暴力做,发现对每个数字可以优化的最大 tab 上限就是 \sqrt{a_i}

第三步看题解

第四步 看完来自己写一个题解

来解解吧

考虑 \Theta(n \log n) 的算法

不难发现,自己想根本想不出

那就考虑一下优化吧

第一步 sort+unique

这样对于相同的数字操作就不会重复了

然后考虑一下计数方式

假如我们用 ans[k] 记录 x=k 的答案

那么每次 k>\sqrt{a_i} 的时候,我们都需要在 ans[k] 上加一个 a_i ,每个数字实际操作仍为 \Theta(n)

反过来一下,我们在赋初值时使每一个 ans[k] 都为 \sum_{i=1}^{n}a_i

对于 k>\sqrt{a_i} ,我们每次都更新答案,减去可以减少的空格数

对于 k\le \sqrt{a_i} ,我们则不做操作

这样就目前来看操作复杂度为 \Theta (n\sqrt{a_i})

考虑枚举过程

容易发现,对于排序过后相邻的 a_i,a_{i+1} ,易有 \lfloor\frac{a_i}{k}\rfloor=\lfloor\frac{a_{i-1}}{k}\rfloor=t

a_i 的个数为 c_i ,那么则有

若对于 i\in [l,r] ,有 \lfloor\frac{a_i}{k}\rfloor=t ,那么 ans[k]-=(\sum_{i=l}^{r}c_i)\cdot t \cdot(k-1)

可用前缀和快速处理

找出最大的 a_i ,令其为 A

那么每一次对 k 枚举 t 时,t 上限就是 \lfloor \frac{A}{k} \rfloor

所以我们选择枚举 k\in [1,n] ,再枚举 t ,每一次枚举 t 都是 \lfloor \frac{A}{k} \rfloor

所以总的复杂度就是 \sum_{k=1}^{n} \lfloor \frac{A}{k} \rfloor=\Theta(n\log n)

可以说是非常优秀了

附上代码

#include <iostream>
#include <cstdio>
#define getchar() *inp++
using namespace std;

char INP[1<<25],*inp=INP;

inline int input()
{
	char c=getchar();int o;
	while(c>57||c<48)c=getchar();
	for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
	return o;
}

long long MA,res,ALL,ans[1000123],C[1000123];

int main()
{
	//freopen("In.in","r",stdin); 
	fread(INP,1,1<<25,stdin);
	int n=input();long long Ai;
	for(int i=1;i<=n;i++)
		Ai=input(),MA=max(MA,Ai),C[Ai]++,ALL+=Ai;
	for(int i=1;i<=MA;i++)C[i]+=C[i-1];
	res=ALL;
	for(int t=2;t<=MA;t++)
	{
		ans[t]=ALL;
		int l=0,r;
		for(long long k=0;k<=MA/t;k++)
		{
			r=min((k+1)*t-1,MA);
			ans[t]-=(C[r]-C[l])*k*(t-1);
			l=r;
		}
		if(res>ans[t])res=ans[t];
	}
	printf("%lld",res);
}