题解/算法 {2968. 执行操作使频率分数最大}

题解/算法 {2968. 执行操作使频率分数最大}

@LINK: https://leetcode.cn/problems/apply-operations-to-maximize-frequency-score/;

{ 做法1 (不好)

将序列排序, 遍历每个元素A[i], 让他往左右扩展 选择代价小的方向, 最终得到[l,r] (即说明A[l...r]都等于A[i]) 更新答案;
然后优化, 即令A[i+1]的答案是ll,rr, 考虑如何从[l,r] 更新到[ll,rr]; 首先, 这个思路其实是不对的, 这并不是滑动窗口! 因为无法证明l,ll; r,rr是存在单调关系的;
但如果硬是这么做, 也是可以转换的;
[l...i...r] 首先转换到[l...i+1...r], 原来的A[i+1...r]这些元素的代价 会都减去一个A[i+1]-A[i]; 而原来的A[l...i]这些元素的代价 都会加上一个A[i+1]-A[i]; 然后此时[l...i+1...r] 她可能是不合法的 (即代价溢出了) 因为你把所有<=A[i]的元素 都加上了一个代价
1: 如果溢出了, 一直把A[l]给删除掉; (比如1,100; K=0, 原来在1时是[0,0] 你现在变成[0,1] 代价溢出了);
2: A[l]与A[i+1]的差距 可能非常大, 如果他比A[r+1]-A[i+1]都大 一直把A[l]给删掉; (比如1,100,101,102...; K=100, 原来在1时是[0,2](代价100), 现在在100[0,2](虽然代价没有溢出) 但是由于abs(1-100)太大 要把1给删除掉 因为[100,101,102,...]才是答案;
3: 此时的[l,...,i+1,...,r]是合法的, 再把他进行扩张;

int maxFrequencyScore(vector<int>& A, long long K) {
    sort( A.begin(), A.end());
    int N = A.size();
    int l, r;
    auto Padding = [&]( int _cur){
        ASSERT_(l<=_cur && r>=_cur);
        while( l>0 || r<N-1){
            if( l>0 && r<N-1){
                auto ll = abs(A[l-1] - A[_cur]), rr = abs(A[r+1] - A[_cur]);
                // 注意比較的是`ll,rr`的大小, 而不是`A[l-1],A[r+1]`的大小 (因為顯然`A[l-1]<=A[r+1]`);
                if( ll<=rr && abs(A[l-1] - A[_cur])<=K){
                    K -= abs(A[l-1] - A[_cur]); --l;
                }
                else if( ll>rr && abs(A[r+1] - A[_cur])<=K){
                    K -= abs(A[r+1] - A[_cur]); ++r;
                }
                else{ break;}
            }
            else if( l > 0){
                if( abs(A[l-1] - A[_cur])<=K){
                    K -= abs(A[l-1] - A[_cur]); --l;
                }
                else{ break;}
            }
            else if( r < N-1){
                if( abs(A[r+1] - A[_cur])<=K){
                    K -= abs(A[r+1] - A[_cur]); ++r;
                }
                else{ break;}
            }
            else{ break;}
        }
    };
    int ANS = 1;
    for( int i = 0; i < N; ++i){
        if( i == 0){
            l = 0, r = 0;
            Padding( 0);
        }
        else{
            K += (r - (i-1)) * 1LL * (A[i]-A[i-1]);
            K -= ((i-1) - l + 1) * 1LL * (A[i]-A[i-1]);
            
            r = max( i, r); // 如果`r==i-1`, 此時要把他更新為`i`;
            
            while( K<0 && l<i){ K += abs(A[l]-A[i]); ++l;}

            //> shrink `l` (注意比較的是`A[r+1]` 而不是`A[r]`, 否則會超時);
            while( r+1<N && l<i && abs(A[l]-A[i])>abs(A[r+1]-A[i])){
                K += abs(A[l] - A[i]);  ++ l;
            }
            Padding( i);
        }
        ANS = max( ANS, r-l+1);
    }
    return ANS;
}

}

上面做法是碰巧的… 因為[l,r] -> [ll,rr] 並沒有單調性;

正確做法是: 排序以後, 假如最終的眾數是T, 那麼他在序列裡 一定是形如: ??? T...T ???, 也就是 她是連續的一段; 即他是一個區間, 因此考慮他是否滿足滑動窗口;
比如他這段是[l,r], 也就意味著 如果我們固定左端點l, 我們考慮 r端點 是否具有單調性; [l, <r]一定是合法的 且[l, >r]一定是不合法的, 詳見@LINK: (https://editor.csdn.net/md/?not_checkout=1&articleId=135069315)-(@LOC_0);
即, 我們令Cost(l,r)表示 (將這個區間的所有元素 變成相同的 的最小曼哈頓距離), 那麼對於ll>=l, rr<=r 則一定有Cost(ll,rr) <= Cost(l,r), 因為有這個性質 所以可以用滑動窗口;

接下來 你需要O(1)的計算出 Cost(l,r)的值, 這可以通過前綴和處理;

    int maxFrequencyScore(vector<int>& A, long long K) {
        sort( A.begin(), A.end());
        int N = A.size();
        PrefixSum_1D.Initialize( N);  FOR_( i, 0, N-1){ PrefixSum_1D.__A[i] = A[i];}  PrefixSum_1D.Work();
        auto Cost = [&]( int _l, int _r){
            auto mid = (_l+_r)>>1;
            auto ANS = A[mid] * 1LL * (mid-_l+1) - PrefixSum_1D.Get_intervalSum(_l, mid);
            if( mid+1 <= _r){
                ANS += ( PrefixSum_1D.Get_intervalSum(mid+1, _r) - A[mid] * 1LL * (_r-mid));
            }
            return ANS;
        };
        int ANS = 1;
        for( int r=0, l = 0; l < N; ++l){
            while( r+1<N && Cost(l,r+1)<=K){
                ++r;
            }
            ANS = max( ANS, r - l + 1);
        }
        return ANS;
    }
  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值