题目描述
难度分:1875
输入n(1≤n≤2×105)和长为n的数组a(1≤a[i]≤1×1015)。
定义f(x)为x的数位和。例如f(123)=1+2+3=6。
输出Σni=1Σnj=1f(a[i]+a[j])。
输入样例1
2
53 28
输出样例1
36
输入样例2
1
999999999999999
输出样例2
135
输入样例3
5
123 456 789 101 112
输出样例3
321
算法
拆位+双指针
这个题首先要能发现一个性质:f(x+y)=f(x)+f(y)−9×k(x,y),其中k(x,y)是计算x+y时产生的进位次数,没有这个性质的话又是坐牢。先化简一下这个公式减号前面的部分:
Σni=1Σnj=1(f(a[i])+f(a[j]))
=Σnj=1Σni=1f(a[i])+Σni=1Σnj=1f(a[j])
=nΣni=1f(a[i])+nΣnj=1f(a[j])
=2nΣni=1f(a[i])
上式通过O(nlog10n)的时间复杂度就能计算出来,而进位次数k(x,y)通过拆位来计算,按顺序检查:
-
有多少a[i]+a[j]时,个位数发生了进位?也就是看a[i]%10+a[j]%10≥10是否成立。
把数组按照a[i]%10排序,然后用相向双指针统计。 -
有多少a[i]+a[j]时,十位数发生了进位?也就是看a[i]%100+a[j]%100≥100是否成立。
把数组按照a[i]%100排序,然后用相向双指针统计。 -
依此类推,直到100…00>2×max(a)时停止。
分别检查“个、十、百、……”这些数位,检查第k位(k=0表示个位,以此类推)时,先将a数组按照a[i]%10k+1排序。然后遍历i∈[1,n],对于每个a[i],利用双指针来确定最后一个不满足a[i]%10+a[j]%10≥10的j,这样满足这个不等式的j就有n−j个。
复杂度分析
时间复杂度
排序+双指针的时间复杂度为O(nlog2n),由于a[i]最大可以达到1015,所以需要遍历的位数最大就是15,是个常数,记为A。预处理出每个元素a[i]的数位和时间复杂度为O(nlog10n),因此算法整体的时间复杂度为O(nlog10n)+O(Anlog2n)。
空间复杂度
排序的额外空间复杂度为O(log2n)(快速排序),而除了排序之外,仅使用了有限几个变量。因此,额外空间复杂度的痛点就在于排序,为O(nlog2n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 200010;
int n;
LL a[N];
// 数位和
int f(LL x) {
int sum = 0;
while(x) {
sum += x%10;
x /= 10;
}
return sum;
}
int main() {
scanf("%d", &n);
LL ans = 0, mx = 0;
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
ans += f(a[i]);
mx = max(mx, a[i]);
}
ans *= 2ll*n;
LL carry = 0;
for(LL pow10 = 10; pow10 <= 2*mx; pow10 *= 10) {
sort(a + 1, a + n + 1, [&](LL x, LL y) {
return x % pow10 < y % pow10;
});
int j = n;
for(int i = 1; i <= n; i++) {
while(j && a[i]%pow10 + a[j]%pow10 >= pow10) {
j--;
}
carry += n - j;
}
}
ans -= 9ll*carry;
printf("%lld\n", ans);
return 0;
}