[HDU 6378] 1005 度度熊玩数组 2018 “百度之星”程序设计大赛 - 初赛(A)

题意

长度为 N(1N100000) N ( 1 ≤ N ≤ 100000 ) 的数组 A A ,和一个整数 K K ,有 N N 次操作每次删除一个位置

问不包含失效位置的最接近 K K 的区间权值和

分析

并没有完全看懂出题者的题解,补题时非常感谢https://blog.csdn.net/qq_36797743/article/details/81604424的启发!

首先对于每次使一个点失效,我们可以将其离线,倒过来做。这样问题就变成每次使一个点生效。

考虑到每次使某点生效,本质为将该点与(已经生效的)左右区间进行合并。对于合并的对象,我们可以考虑到,保存每个区间的值是不可行的。那么我们可以保存某个区间内所有的前缀和(或后缀和),由于 [l,r] [ l , r ] 区间和为 SrSl1 S r − S l − 1 ,我们可以在两个区间进行合并时,通过“启发式方法”(通过枚举小区间内的前缀值,在另一区间内查找使得最接近 K K 的另一前缀和,然后将较小的区间放到较大区间中),同时计算出 区间和 K K 差的绝对值的最小值。

具体的操作是为每一个位置开一个 set s e t ,之后固定枚举区间的左端点或右端点:

  • 当左区间 Size S i z e ≤ 右区间 Size S i z e 时,对于每一个左边界 D=|K(SrSl1)|=|(K+Sl1)Sr| D = | K − ( S r − S l − 1 ) | = | ( K + S l − 1 ) − S r |

    只要在右区间中查询 SiK+Sl1Sj S i ≤ K + S l − 1 ≤ S j ,之后更新最小值 D D

    之后将左区间合并入右区间中。

  • 当右区间 Size< S i z e < 左区间 Size S i z e 时,对于每一个右边界 D=|K(SrSl1)|=|(SrK)Sl1| D = | K − ( S r − S l − 1 ) | = | ( S r − K ) − S l − 1 |

    只要在左区间中查询 SiSrKSj S i ≤ S r − K ≤ S j ,之后更新最小值 D D

    之后将右区间合并入左区间中。

最后要考虑的是当我们对每一个点 i i 创建 set s e t 时,只插入了 Si S i ,而 [l,r] [ l , r ] 区间和的表示是 SrSl1 S r − S l − 1 ,那么就会漏掉 l=leftmost i l = leftmost  i 的情况,我们可以在每次区间合并前,将 leftmost i leftmost  i 当作左边界强制更新一下最小值。

显而易见的是,通过启发式方法进行合并的均摊时间复杂度为 O(nlogn) O ( n log ⁡ n ) ,由于在 set s e t 中的查询需要 logn log ⁡ n 的时间,故整体时间复杂度为 O(nlog2n) O ( n log 2 ⁡ n )

代码

/*C++11 WARNING*/
#include
using namespace std;
typedef long long ll;
const int N = 1e5+5;
int n, k;
int a[N], s[N], f[N], lm[N], d[N], ans[N];
set<int> st[N];
int findfa(int x){return x==f[x]?x:f[x]=findfa(f[x]);}
void init(int n){
    memset(f, 0, (n+2)*sizeof(int));
    for(int i = 1; i <= n; i++) lm[i] = i-1;
}

void mergeSet(int x, int y, int& mn){
    int fx = findfa(x), fy = findfa(y);
    ll sp = (ll)k+s[lm[fx]];
    auto iter = st[fy].lower_bound(sp);
    if(iter != st[fy].end()) mn = min((ll)mn, abs(sp-*iter));
    if(iter != st[fy].begin()){iter--; mn = min((ll)mn, abs(sp-*iter));}
    if(st[fx].size() >= st[fy].size()){
        f[fy] = fx;
        for(int v: st[fy]){
            sp = (ll)v-k;
            iter = st[fx].lower_bound(sp);
            if(iter != st[fx].end()) mn = min((ll)mn, abs(sp-*iter));
            if(iter != st[fx].begin()){iter--; mn = min((ll)mn, abs(sp-*iter));}
        }
        for(int v: st[fy]) st[fx].insert(v);
        st[fy].clear();
    }else{
        f[fx] = fy; lm[fy] = lm[fx];
        for(int v: st[fx]){
            sp = (ll)k+v;
            iter = st[fy].lower_bound(sp);
            if(iter != st[fy].end()) mn = min((ll)mn, abs(sp-*iter));
            if(iter != st[fy].begin()){iter--; mn = min((ll)mn, abs(sp-*iter));}
        }
        for(int v: st[fx]) st[fy].insert(v);
        st[fx].clear();
    }
}

int main()
{
    while(scanf("%d%d", &n, &k) != EOF){
        init(n);
        for(int i = 1; i <= n; i++){
            scanf("%d", &a[i]);
            s[i] = s[i-1] + a[i];
        }
        for(int i = 1; i <= n; i++) scanf("%d", &d[i]);
        int mn = 0x7fffffff;
        for(int i = n; i >= 1; i--){
            if(mn == 0) {ans[i] = 0; continue;}
            int& x = d[i];
            mn = min(mn, abs(a[x] - k));
            st[x].clear();
            st[x].insert(s[x]);
            f[x] = x;
            if(f[x-1]) mergeSet(x-1, x, mn);
            if(f[x+1]) mergeSet(x, x+1, mn);
            ans[i] = mn;
        }
        for(int i = 1; i <= n; i++) printf("%d\n", ans[i]);
    }
    return 0;
}

你可能感兴趣的:([HDU 6378] 1005 度度熊玩数组 2018 “百度之星”程序设计大赛 - 初赛(A))