zoukankan      html  css  js  c++  java
  • CF1039E Summer Oenothera Exhibition

    题目链接

    CF1039E Summer Oenothera Exhibition

    题目大意

    给定一个长度为 (n)​​ 的序列 (a_i)​​ 和一个数字 (w)​,(q)​ 次询问,每次给出 (k)​,要求将 (a_i)​ 分成若干段,满足一段内的极差 (max-minleq w-k)​​, 求最小段数。

    (1leq n,qleq 10^5)(a_i,k,wleq 10^9)

    时限 (7000;ms)

    思路

    这个题主要有两个做法,一个是 (O(n^{frac{5}{3}}+n^{frac{4}{3}}log n))​​ 的分块做法,一个是 (O(nsqrt nlog n))(LCT)​ 加根号分治做法,不过它也可以被 (O(nq))​ 指令集优化的做法卡过去。

    做法一

    不难想到贪心做法,我们从 (a_1)​ 开始,往后能取就取,若取不了就只能再分一段。这样朴素地做是 (O(nq)) 的,然后可以发现这个东西看起来非常可以分块,于是有如下思路:

    (nxt_i) 为在当前 (k) 下,从 (a_i) 往后的第一个不可以从 (i)​ 到这里分为一段的位置,而刚才的贪心过程就相当于从 (1) 开始不断跳 (nxt_i)​ 直到序列末尾,而答案即为跳的次数。为了处理方便,将 (k) 按照 (w-k) 从小到大排序,这样可以保证 (nxt_i)​ 是一直往前延伸的。

    设一个块长 (A)​,我们把一个块内的 (nxt)​​ 压缩起来,记 (suf_i)​ 为在 (i)​ 一直跳 (nxt)​ 的这条链上,最后一个 (<i+A)​ 的位置,即 (suf_i-i<A)​,(nxt_{suf_i}-igeq A)​,且 (suf_i)​ 和 (i)​ 在同一条链上,另设 (cnt_i) 为从 (i) 跳到 (suf_i) 一共跳了多少次,这样查询答案时直接跳 (suf_i) 即可,每次答案加上 (cnt_i)。由于只需要关注 ([i,i+A)) 内的情况,我们的 (nxt_i) 也只处理这么多,当 (nxt_igeq i+A) 时就不管它了。

    我们需要维护的 (nxt_iin[i,i+A)),所以它最多会被修改 (A) 次,每次修改的时候相当于 (i) 以及它往前的链(实际上是一棵 (i) 为子节点,(nxt_i) 为父亲的树)从一条链上拆下来,拼到另一条链上,所以 ((i-A,i]) 中和 (i) 在同一链上的点的 (suf_i)(cnt_i)​​ 都会发生变化,这个可以 (O(A)) 地进行修改。从而总变化的时间复杂度是 (O(nA^2)) 的。

    回到查询,注意到跳 (suf_i)​ 时可能会出现 (suf_i=i)​ 的情况,即 (nxt_igeq i+A)​,这里的 (nxt)​ 我们没有去维护,那就用倍增的方式去找 (nxt_i)​,用 (ST)​ 表来维护区间极差。而现在我们每一步的距离都至少为 (A)​,从而查询的时间复杂度是 (O(qfrac{n}{A}log n))​ 的。

    目前总时间复杂度 (O(nA^2+qfrac{n}{A}log n)),把 (n)(q) 当成同阶的,取 (A=n^{frac{1}{3}}) 会有较小值 (O(n^{frac{5}{3}}+n^{frac{5}{3}}log n))(我也不知道为啥不取 (A=sqrt[3] {nlog n})),然而这个连 (O(n^2)) ​的暴力都不如诶!

    观察式子,左边的 (O(n^{frac{5}{3}}))​​ 已经够通过此题了,复杂度瓶颈在于右边的倍增查询,那咋办呢?目前可行的做法似乎只有减少倍增次数了,观察先前的算法,对于 (nxt_igeq i+A) 的部分,我们是直接弃疗的,问题就出在这里,如果我们再多维护一点 (nxt_i) 呢?

    注意到当 (nxt_igeq i+A) 时,(nxt_i) 的变化就不会影响任何一个点的 (suf)(cnt) 了,就是说如果要维护的话,我们可以直接 (O(1))​ 地做。

    于是再设一个块长 (B),当 (i+Aleq nxt_i<i+B) 时,我们依然维护 (nxt_i),因为是 (O(1)) 的,且 (nxt_i) 在这种情况下最多被修改 (O(B)) 次,所以这一块的时间复杂度是 (O(nB)) 的。现在需要倍增的次数就降到了 (O(frac{n}{B})),从而总时间复杂度变成了 (O(nA^2+frac{n^2}{A}+nB+frac{n^2}{B}log n)),取 (A=n^{frac{1}{3}},B=n^{frac{2}{3}})​,我们获得了 (O(n^{frac{5}{3}}+n^{frac{4}{3}}log n)) 的优秀复杂度。

    然后就做完了,总结一下,这个分块的做法是一种分三类的分治:

    1. (nxt_i-i<n^{frac{1}{3}}):块内路径压缩加速
    2. (n^{frac{1}{3}}leq nxt_i-i<n^{frac{2}{3}}):暴力维护 (nxt_i) 的跳转情况
    3. (nxt_i-ileq n^{frac{2}{3}})​:用倍增配合 (ST) 表找到下一个跳转位置

    实现细节

    • 可以用一个小根堆维护 (i)(nxt_i) 之间的极差,方便每次 (k) 变化时更新对应的 (nxt_i)
    • (Aleq nxt_i-i<B) 时不要把 (i) 丢小根堆里,否则这样子总共要更新的 (nxt_i)(O(n^{frac{5}{3}}log n)) 级别的,加之 C++ 的 priority_queue 常数大,是不可能卡过去的,笔者曾因为这个 (TLE) 到怀疑人生。正确的处理方式是等要用到 (nxt_i) 时,再更新这类情况的 (nxt)​ 值。

    Code

    // Decomposition solution 
    // O(n^(5/3)+n^(4/3)logn)
    
    #pragma GCC optimize("O3")
    #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,tune=native")
    #pragma GCC optimize("inline","fast-math","unroll-loops","no-stack-protector")
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #include<fstream>
    #define rep(i,a,b) for(int i = (a); i <= (b); i++)
    #define per(i,b,a) for(int i = (b); i >= (a); i--)
    #define N 100100
    #define A 47    // N^(1/3)
    #define B 2155  // N^(2/3)
    #define LOG 18
    #define PII pair<int, int>
    #define fr first
    #define sc second
    #define Inf 0x3f3f3f3f
    using namespace std;
    
    inline int read(){
        int s = 0, w = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar(); }
        while(ch >= '0' && ch <= '9') s = (s<<3)+(s<<1)+(ch^48), ch = getchar();
        return s*w;
    }
    
    struct query{
        int diff, id;
        bool operator < (const query b) const{ return diff < b.diff; }
    } ask[N];
    
    int a[N], ans[N];
    int n, w, qy;
    
    int stmx[LOG][N], stmn[LOG][N], log[1<<LOG];
    
    int mn[N], mx[N], nxt[N], suf[N], cnt[N];
    int chain[N];
    priority_queue<PII, vector<PII>, greater<PII> > q;
    vector<int> frward, backward;
    
    void init(){
        per(i,n+1,1){
            stmn[0][i] = stmx[0][i] = a[i];
            rep(p,1,LOG) if(i+(1<<p) <= n+2){
                stmn[p][i] = min(stmn[p-1][i], stmn[p-1][i+(1<<(p-1))]);
                stmx[p][i] = max(stmx[p-1][i], stmx[p-1][i+(1<<(p-1))]);
            }
        }
        rep(i,0,LOG-1) log[1<<i] = i;
        rep(i,2,n) if(!log[i]) log[i] = log[i-1];
    }
    
    int extreme_diff(int l, int r){
        int p = log[r-l+1];
        return max(stmx[p][l], stmx[p][r-(1<<p)+1]) - min(stmn[p][l], stmn[p][r-(1<<p)+1]);
    }
    
    int dis(int j, int i){ return max(abs(a[j]-mn[i]), abs(a[j]-mx[i])); }
    
    bool ok(int j, int i, int d){
        if(dis(j, i) <= d)
            mn[i] = min(mn[i], a[j]), mx[i] = max(mx[i], a[j]);
        else return false;
        return true;
    }
    
    void update_chain(int i, int d){
        frward.clear(), backward.clear();
        int tmp = nxt[i];
        while(tmp <= n && tmp-i < B && ok(tmp, i, d)) tmp++;
        nxt[i] = tmp; 
        tmp = i, cnt[i] = 0, frward.push_back(i);
        while(tmp <= n && nxt[tmp]-i < A) cnt[i]++, tmp = nxt[tmp], frward.push_back(tmp);
        suf[i] = tmp;
        chain[i] = 1, backward.push_back(i);
    
    
        int num = cnt[i];
        per(j, i, max(1, i-A+1)) if(chain[nxt[j]]){
            backward.push_back(j);
            while(!frward.empty() && frward.back()-j >= A) frward.pop_back(); 
            if(frward.empty()) break;
            cnt[j] = chain[nxt[j]]+frward.size()-1;
            chain[j] = chain[nxt[j]]+1;
            suf[j] = frward.back();
        }
        for(int j : backward) chain[j] = 0;
    }
    
    int main(){
        n = read(), w = read(), qy = read();
        rep(i,1,n) a[i] = read();
        a[n+1] = Inf;
        rep(i,1,qy) ask[i].diff = w-read(), ask[i].id = i;
    
        sort(ask+1, ask+qy+1);
        rep(i,1,n){
            nxt[i] = i+1, suf[i] = min(i+A-1, n+1);
            cnt[i] = min(n+1, suf[i]) - i, q.push({abs(a[nxt[i]]-a[i]), -i});
            mn[i] = mx[i] = a[i];
        }
        init();
    
        rep(p,1,qy){
            int d = ask[p].diff;
            while(!q.empty() && d >= q.top().fr){
                int i = -q.top().sc; q.pop();
                if(nxt[i]-i >= B || nxt[i] > n) continue;
    
                if(nxt[i]-i < A) update_chain(i, d);
                else while(nxt[i] < i+B && ok(nxt[i], i, d)) nxt[i]++;
                if(nxt[i] < i+A) q.push({dis(nxt[i], i), -i});
            }
    
            int i = 1, tot = 0;
            while(i <= n)
                if(suf[i] > i) tot += cnt[i], i = suf[i];
                else{
                    tot++;
                    while(nxt[i] < i+B && ok(nxt[i], i, d)) nxt[i]++;
                    if(nxt[i]-i < B) i = nxt[i];
                    else{
                        int j = i;
                        per(p,LOG-1,0){
                            int up = j+(1<<p);
                            if(up <= n+1 && extreme_diff(i, up) <= d) j = up;
                        }
                        i = j+1;
                    }
                }
            ans[ask[p].id] = tot-1;
        }
    
        rep(i,1,qy) printf("%d
    ", ans[i]);
        return 0;
    }
    

    做法二

    (前面的分析和上一个做法相同,没看的可以翻上去看一眼)

    刚才我们已经有了一个 (i)(nxt_i) 连边的概念,但是没有充分利用这个性质。多建一个节点 (a_{i+1}=Inf),把图画出来可以发现是一棵以 (n+1) 为根的树,而我们询问的即为从 (1)(n+1) 这条树链的长度,由于树的形态会改变,容易想到用 (Lint;Cut;Tree) 去维护这个信息。

    此处大部分题解都提到了 弹飞绵羊 这道题,其实就是个简单序列建树然后 (LCT)​ 的板子题?不管了如果您感兴趣的话那也去看一眼吧。

    由于节点的变化 (O(n))​​​,暴力维护 (LCT)​​​ 是 (O(n^2log n))​​​ 的,还没暴力快,但是此时查询的时间复杂度只有 (O(nlog n))​​​​​ (默认 (n,q)​ 同阶),于是考虑用根号分治平衡两者的时间复杂度。

    这时间复杂度关系看起来就知道最后应该两者都是 (O(nsqrt n log n))​ 的,具体来说,既然只维护 (nxt_i)(O(sqrt n)) 次变化,那么我们就只在 (nxt_i< i+sqrt n) 时在 (LCT) 上连边,否则就不管它了。这个时候可能会存在总共 (O(sqrt n))​ 棵 (LCT),在询问时,(LCT) 内从当前点 (O(log n))​ 跳到树根并统计链长,到了树根之后,再用 (ST) 表配合倍增找到下一个 (nxt_i),在树与树之间跳跃(此时答案要加 (1)),最终到达 (n+1) 节点。

    处理变化,以及查询时树内统计和树间跳跃的时间复杂度都是 (O(nsqrt nlog n)) 的,于是我们用 (LCT)​ 加根号分治的做法解决了本题。虽然跑起来比做法一要慢,但是脱离数据范围从时间复杂度上来看,这个做法是更优的。

    实现细节

    • 这里需要保证 (nxt_i) 任意时刻都是 (i) 的父亲,也就是说 (LCT) 的操作不能进行 (make\_root),所以 (link)(cut) 的时候直接 (access)​ 然后修改就可以了,同时操作的两个节点的父子关系也要注意一下。
    • (O(nsqrt n log n)) 的运算量达到了 (5e8),需要注意 (LCT) 的常数优化(就算如此 (O(n^2)) 还能卡过去真是一件离谱的事情),如 (splay) 的时候不要用 STL 的 (stack)​,而要手写,还有由于已经保证了所有操作的合法性,在 (link,cut)​ 的时候就不要再判了。((TLE) 到怀疑人生 *2)
    • 由于 (nxt) 会更新 (O(nsqrt n)) 次,有 (3.17e7) 了,更新 (nxt_i)​ 的部分不能采取做法一用小根堆维护的方法,应把要改变的 (nxt_i)(vector) 存到对应的询问那里,找对应询问二分即可。((TLE) 到怀疑人生 *3)

    Code

    // LCT and sqrt block solution
    // O(n^(3/2)logn)
    
    #pragma GCC optimize("O3")
    #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,tune=native")
    #pragma GCC optimize("inline","fast-math","unroll-loops","no-stack-protector")
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<vector>
    #define rep(i,a,b) for(int i = (a); i <= (b); i++)
    #define per(i,b,a) for(int i = (b); i >= (a); i--)
    #define N 101000
    #define B 317
    #define LOG 18
    #define Inf 0x3f3f3f3f
    #define l(x) t[x].c[0]
    #define r(x) t[x].c[1]
    #define f(x) t[x].fa
    #define siz(x) t[x].siz
    #define tree(x) LCT.S.t[x]
    #define PII pair<int, int>
    #define fr first
    #define sc second
    
    using namespace std;
    
    inline int read(){
        int s = 0, w = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar(); }
        while(ch >= '0' && ch <= '9') s = (s<<3)+(s<<1)+(ch^48), ch = getchar();
        return s*w;
    }
    
    struct Splay{
        struct node{
            int c[2], siz, fa, tag;
            node(){ c[0] = c[1] = siz = fa = tag = 0; }
        } t[N];
        int s[N];
    
        bool isroot(int x){
            return l(f(x)) != x && r(f(x)) != x;
        }
        void flip(int x){
            swap(l(x), r(x)), t[x].tag ^= 1;
        }
        void push_up(int x){ siz(x) = siz(l(x))+siz(r(x))+1; }
        void push_down(int x){
            if(t[x].tag) rep(i,0,1)
                if(t[x].c[i]) flip(t[x].c[i]);
            t[x].tag = 0;
        }
    
        void rotate(int x){
            int y = f(x), z = f(y);
            int dir1 = (r(y) == x), dir2 = (r(z) == y);
            if(!isroot(y)) t[z].c[dir2] = x;
            f(x) = z;
            int son = t[x].c[dir1^1];
            t[y].c[dir1] = son, f(son) = (son ? y : 0);
            t[x].c[dir1^1] = y, f(y) = x;
            push_up(y);
        }
    
        void splay(int x){
            int y, z, tmp = x, top = 0;
            while(!isroot(tmp)) s[++top] = tmp, tmp = f(tmp);
            s[++top] = tmp;
            while(top) push_down(s[top--]);
            while(!isroot(x)){
                y = f(x), z = f(y);
                if(!isroot(y)) rotate((l(y) == x)^(l(z) == y) ? x : y);
                rotate(x);
            }
            push_up(x);
        }
    };
    
    struct Link_Cut_Tree{
        Splay S;
    
        void access(int x){
            for(int y = 0; x; x = S.f(y = x))
                S.splay(x), S.r(x) = y, S.push_up(x);
        }
    
        void make_root(int x){
            access(x), S.splay(x), S.flip(x);
        }
        int find_root(int x){
            access(x), S.splay(x);
            while(S.l(x)) S.push_down(x), x = S.l(x);
            S.splay(x);
            return x;
        }
    
        void split(int x, int y){
            make_root(x), access(y), S.splay(y);
        }
        void link(int x, int y){
            access(x), S.f(x) = y;
        }
        void cut(int x, int y){
            access(x), S.splay(y);
            if(S.f(x) == y && S.r(y) == x)
                S.f(x) = S.r(y) = 0, S.push_up(y);
        }
    } LCT;
    
    int a[N], ans[N];
    PII ask[N];
    int n, w, qy;
    
    int nxt[N], mn[N], mx[N];
    int stmin[N][LOG], stmax[N][LOG], Log[1<<LOG];
    vector<int> extend[N];
    
    void init(){
        per(i,n+1,1){
            stmin[i][0] = stmax[i][0] = a[i];
            rep(p,1,LOG-1) if(i+(1<<p) <= n+2){
                stmin[i][p] = min(stmin[i][p-1], stmin[i+(1<<(p-1))][p-1]);
                stmax[i][p] = max(stmax[i][p-1], stmax[i+(1<<(p-1))][p-1]);
            }
        }
        rep(i,0,LOG-1) Log[1<<i] = i;
        rep(i,2,n) if(!Log[i]) Log[i] = Log[i-1];
    }
    
    int diff(int l, int r){
        int p = Log[r-l+1];
        return max(stmax[l][p], stmax[r-(1<<p)+1][p]) - min(stmin[l][p], stmin[r-(1<<p)+1][p]);
    }
    
    int dis(int i, int j){
        return max(abs(mx[i]-a[j]), abs(mn[i]-a[j]));
    }
    bool check(int i, int j, int d){
        if(dis(i, j) > d) return false;
        mx[i] = max(mx[i], a[j]), mn[i] = min(mn[i], a[j]);
        return true;
    }
    
    int main(){
        n = read(), w = read(), qy = read();
        rep(i,1,n) a[i] = read();
        a[n+1] = Inf, init();
        rep(i,1,qy) ask[i] = {w-read(), i};
        ask[qy+1] = {Inf, 0};
    
        sort(ask+1, ask+1+qy);
        rep(i,1,n){
            LCT.link(i, i+1), nxt[i] = i+1;
            mn[i] = mx[i] = a[i];
            extend[lower_bound(ask+1, ask+qy+2, make_pair(abs(a[i+1]-a[i]), 0)) - ask].push_back(i);
        }
    
        rep(p,1,qy){
            int d = ask[p].fr;
            for(int i : extend[p]){
                int j = nxt[i];
                while(j <= n && j < i+B && check(i, j, d)) j++;
                LCT.cut(i, nxt[i]);
                nxt[i] = j;
                if(j < i+B){
                    LCT.link(i, nxt[i]);
                    extend[lower_bound(ask+p+1, ask+qy+2, make_pair(dis(i, j), 0)) - ask].push_back(i);
                }
            }
    
            int i = 1, tot = 0;
            while(i <= n){
                int j = LCT.find_root(i);
                tot += tree(j).siz-1, i = j;
                if(j == n+1) break;
                per(p,LOG-1,0) if(j+(1<<p) <= n+1)
                    if(diff(i, j+(1<<p)) <= d) j += (1<<p);
                i = j+1, tot++;
            }
            ans[ask[p].sc] = tot-1;
        }
    
        rep(i,1,qy) printf("%d
    ", ans[i]);
        return 0;
    }
    

    完结撒花。

  • 相关阅读:
    调试
    webpack output的path publicPath
    CSS实现单行、多行文本溢出显示省略号
    docker安装mysql
    构建docker镜像
    Tensorflow博文列表
    ML理论知识博文列表
    Python博文列表
    Opencv博文收藏列表
    Centos文章列表
  • 原文地址:https://www.cnblogs.com/Neal-lee/p/15091553.html
Copyright © 2011-2022 走看看