题解/算法 {E - Max/Min}
@LINK: https://atcoder.jp/contests/abc356/tasks/abc356_e
;
我们可以将数组排序 这不影响结果, 比如[1, 2, 2, 4, 5, 6, 6]
, 此时有两种遍历方式 {从小到大枚举, 从大到小枚举};
#超时错误#: 如果用从大到小枚举, 即对于[i]
元素来说 判断所有[<i]
下标的元素的A[i]/A[<i]
下取整之和;
自然我们可以想到下取整函数, 因为A[i]/?
的值域 只有sqrt(A[i])
个;
int N; cin>> N;
static int Sum[ 1000006]; std::memset( Sum, 0, sizeof( Sum));
std::vector<int> A(N); for( auto & i : A){ cin>> i; Sum[i] ++;}
FOR_( i, 1, 1000006-6){ Sum[i] += Sum[i-1];}
std::sort( A.begin(), A.end(), std::greater<>());
Int64_ ANS = 0;
for( auto a : A){
Sum[a] --; // 你删除一个`a` 她会影响所有的`Sum[>=a]` 但我们只修改了`Sum[a]` 这没问题 不产生后效性 因为我们以后不会使用`Sum[>a]`;
for( int l = 1; l <= a;){
auto r = (a / (a / l)); // `a / [l-r]`的值是一样的
ANS += (Sum[r] - Sum[l-1]) * 1LL * (a / l);
l = r+1;
}
}
cout<< ANS<< "\n";
但是 这是超时的… 2e5 * sqrt(1e6) = 2e8
看着没问题 但由于有memset, sort
她会超时…
我们再看另一个思路, 即从小到大, 对于一个数x
[x*1, x*2)
区间里每个数的?/x
下取整值是1
, [x*2, x*3)
里每个数的值是2
, [x*3, x*4)
里每个数的值是3
, …; 她的时间 就快很多 因为是每个数x
划分为1e6/x
个长度为x
的区间, 即调和级数 即1e6 + 1e6/2 + 1e6/3 + ... + 1e6/1e6 = 1e6 * ln(1e6)
;
.
但有一点很麻烦 因为我们是从小到大枚举… 你不能向上面代码一样 去枚举每个元素A[i]
然后每次把Sum[A[i]] --
, 因为她会产生后效性 因为我们以后会使用到Sum[ >A[i]]
即你需要对Sum[ >=A[i]]
都统一执行-=1
, 这就麻烦了;
.
因此 换种思路, 别枚举元素了 直接枚举值 即[1,2,3,...,1e6]
, 可这样 又有一点很麻烦 因为有重复元素 比如对于当前值v
我们计算[v, 2v), [2v, 3v), ...
的贡献 对于任意区间[?, ?)
令他区间里元素个数是C
当前值v
的个数是CC
那么贡献是C*CC
, 可是区间[v, 2v)
他是错误的 因为v, v
的答案 不是C*C
所以要特判;
先看一个错误做法:
cur = Sum[a] - Sum[a-1];
for( int r = a + a; r <= int(1e6); r += a){ // [r-a, r)
ANS += ((Sum[r-1] - Sum[r-a-1]) *1LL* cur * (r/a - 1));
}
ANS += (cur *1LL* (Sum[int(1e6)] - Sum[int(1e6)-1]) * (int(1e6) / a));
ANS -= (cur *1LL * cur);
ANS += ((1 + cur-1) *1LL* (cur-1) / 2);
由于我们区间枚举r
没有枚举1e6
所以我们单独处理了1e6
的情况, 可是 这是错误的…
对于区间[l, r)
如果我们枚举区间左端点 他的取值是[a, 1e6]
, 可是 你枚举右端点 他的取值可不是[a+a, 1e6]
而是[a+a, 2e6]
所以上面代码是错误的 比如a==5e5
那么你上面的代码 不会计算到位于(5e5, 1e6)
区间里的数, 总之你要保证 你枚举的区间 要包含所有[a, 1e6]
的元素, 因此 你别枚举r
了 直接枚举l
左端点多方便;
#正确做法#
int N;
cin>> N;
static int Sum[ 1000006];
std::memset( Sum, 0, sizeof( Sum));
std::vector<int> A(N); for( auto & i : A){ cin>> i; Sum[i] ++;}
FOR_( i, 1, 1000006-6){ Sum[i] += Sum[i-1];}
Int64_ ANS = 0;
FOR_( a, 1, int(1e6)){
auto cur = Sum[a] - Sum[a-1];
if( cur == 0){ continue;}
for( int l = a; l <= int(1e6); l += a){
// [l, l+a)
ANS += ((Sum[ std::min(int(1e6), l+a-1) ] - Sum[ l-1 ]) *1LL* cur * (r/a));
}
//>> 对于`(a,a)`的情况 特判;
ANS -= (cur *1LL * cur);
ANS += ((1 + cur-1) *1LL* (cur-1) / 2);
}
cout<< ANS<< "\n";