题解/算法 {E - Max/Min}

题解/算法 {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";
  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值