1. 程式人生 > 其它 >[CF407E]k-d-sequence

[CF407E]k-d-sequence

同餘問題,最經典的解決辦法竟然是......線段樹!

壹、題目描述 ¶

傳送門 to Luogu

貳、題解 ¶

一般情形,如果 \(d\neq 0\),那麼一段區間 \([L,R]\) 合法的基礎條件是

\[\forall i,j\in [L,R],a_i\equiv a_j\pmod d\;\and\;\forall i,j\in[L,R],a_i\neq a_j \]

那麼,我們得特別處理一下 \(d=0\),可以 \(\mathcal O(n)\) 掃一遍,不贅述。

對於 \(d>0\) 的情況,我們可以將一段取模 \(d\) 餘數相同的區間拿出來單獨處理。

不過這裡還有個小問題,如何排除相同的數字反覆出現?我們可以使用類似 $\rm two-pointer$ 的方法,移動 $R$ 時,看一看 $a_R$ 上一次出現的位置,與 $L$ 取 $\max$ 即可,但下文結合需要所求,使用了一種更有效的處理方法,會被提及。

現在,我們有了一段可能會出現答案的區間 \([L,R]\),並將它們拿出來單獨處理,我們假設將它們放到一個新的陣列 \(b\) 上,現在,我們考慮如何在這個陣列上得到合法的區間。我們可以先令 \(b_i\leftarrow \left\lfloor{b_i\over d}\right\rfloor\),因為我們不用擔心餘數問題了。

先搞清楚限制條件,對於 \(b\) 的一段區間 \([L,R]\),合法的條件是

\[\max-\min+1-(R-L+1)\le k\Rightarrow \max-\min+L-R\le k \]

也就是我們需要得到一個區間的最大或最小值,但是還有 \(L,R\)

,這怎麼處理?

考慮依次移動右端點,維護每個左端點對應上述的值,那麼,現在右端點在我們手上,即右端點可以看做是 “固定” 的,對於每個左端點,我們需要維護 \([L,R]\) 的最大值和最小值,這個時候引入單調棧+線段樹!具體來說,維護單增單減的兩個棧,單增棧維護最小值,單減棧維護最大值,可以這樣看:

對於紅色的這個值,它負責的就是 \([i+1,j]\) 這個區間的最小值。

或者說我們為什麼要使用單調棧,其本質原因是隨著 \(L\) 的變小,它的 最小值/最大值 只會逐漸往極端走,不可能會 “迴轉”。

對於單減的棧原理也是一樣。

當我們移動 \(R\) 時,要將 \(b_R\) 加入這兩個棧,舉單增的棧(維護最小值)來說:當我們要將 \(i\)

彈出棧時,設 \(i\) 後一個元素為 \(j\),我們需要將 \([j+1,i]\) 都先加上 \(b_i\),因為這原來是 \(b_i\) 負責這段區間的最小值,現在換成 \(b_R\) 後,先將 \(b_i\) 的影響去掉,當我們遇到 \(k\) 而彈不動時,退出迴圈,再將 \([k+1,R]\) 都整體減去 \(b_R\),因為此時 \(b_R\) 是這段區間的最小值。

而當我們移動 \(R\) 時,要注意將整個區間(不同於 \([1,R]\))都減去 \(1\),而將 \(R\) 這個單點加上 \(R\)(因為有 \(+L\) 這個項,而線段樹本質是在維護每個 \(L\) 對應的上述表示式的值,而 \(R\) 同時也是一個新的左端點)。

處理完這些之後,線上段樹上二分找到最小的 \(L\) 滿足上述表示式小於等於 \(k\) 即可。

但是需要處理一個問題 —— 出現重複的數,假設加入 \(b_R\),發現其重複出現,上一次出現的位置在 \(p\),那麼我們只需要令所有 \([1,p]\) 的線段樹上的值為 \(+\infty\) 即可 —— 這樣線段樹二分時這段區間必然不滿足其值小於等於 \(k\).

總的複雜度就是 \(\mathcal O(n\log n)\) 了。

叄、參考程式碼 ¶

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<map>
#include<stack>
using namespace std;

// #define NDEBUG
#include<cassert>

namespace Elaina{
    #define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
    #define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
    #define fi first
    #define se second
    #define mp(a, b) make_pair(a, b)
    #define Endl putchar('\n')
    #define mmset(a, b) memset(a, b, sizeof a)
    // #define int long long
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    typedef pair<ll, ll> pll;
    template<class T>inline T fab(T x){ return x<0? -x: x; }
    template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
    template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
    template<class T>inline T readin(T x){
        x=0; int f=0; char c;
        while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
        for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
        return f? -x: x;
    }
    template<class T>inline void writc(T x, char s='\n'){
        static int fwri_sta[1005], fwri_ed=0;
        if(x<0) putchar('-'), x=-x;
        do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
        while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
        putchar(s);
    }
}
using namespace Elaina;

const int maxn=2e5;
const int inf=1e9+1;

int n, k, d;
int a[maxn+5];

inline void input(){
    n=readin(1), k=readin(1), d=readin(1);
    rep(i, 1, n) a[i]=readin(1)+inf;
}

namespace special{
    inline int launch(){
        int len=1, l=1, cur=1;
        rep(i, 2, n){
            if(a[i]==a[i-1]) ++cur;
            else cur=1;
            if(cur>len) len=cur, l=i-cur+1;
        }
        writc(l, ' '), writc(l+len-1);
        return 0;
    }
}

int len, l, s;
namespace saya{
    ll tag[maxn<<2|2], mn[maxn<<2|2];
    #define ls (i<<1)
    #define rs (i<<1|1)
    #define mid ((l+r)>>1)
    #define _lq ls, l, mid
    #define _rq rs, mid+1, r
    void build(int i, int l, int r){
        tag[i]=mn[i]=0;
        if(l==r) return;
        build(_lq), build(_rq);
    }
    inline void update(int i, ll x){ tag[i]+=x, mn[i]+=x; }
    inline void pushup(int i){ mn[i]=min(mn[ls], mn[rs]); }
    inline void pushdown(int i){
        update(ls, tag[i]), update(rs, tag[i]);
        tag[i]=0;
    }
    void modify(int i, int l, int r, int L, int R, ll x){
        if(L<=l && r<=R) return update(i, x);
        if(tag[i]) pushdown(i);
        if(L<=mid) modify(_lq, L, R, x);
        if(mid<R) modify(_rq, L, R, x);
        pushup(i);
    }
    int query(int i, int l, int r, int k){
        if(mn[i]>k) return -1;
        if(l==r) return l;
        if(tag[i]) pushdown(i);
        if(mn[ls]<=k) return query(_lq, k);
        return query(_rq, k);
    }
    void check(int i, int l, int r){
        if(l==r) return printf("val[%d] == %lld\n", l, mn[i]), void();
        if(tag[i]) pushdown(i);
        check(_lq), check(_rq);
    }
    #undef ls
    #undef rs
    #undef mid
    #undef _lq
    #undef rq
}

map<int, int>pos;
stack<int>up, down;
int b[maxn+5];
inline void solve(int L, int R){
    /** prelude */
    s=R-L+1;
    rep(i, L, R) b[i-L+1]=a[i]/d;
    
    /** some clear */
    saya::build(1, 1, s); pos.clear();
    while(!up.empty()) up.pop();
    while(!down.empty()) down.pop();
    up.push(0), down.push(0);
    /** enumerate the right endpos */
    rep(i, 1, s){
        saya::modify(1, 1, s, 1, s, -1); saya::modify(1, 1, s, i, i, i);

        /** @p inf shoule be large enough, but it shouldn't to too large */
        if(pos.count(b[i])) saya::modify(1, 1, s, 1, pos[b[i]], inf);
        pos[b[i]]=i;

        /** maintain the minimum value */
        while(up.top() && b[up.top()]>b[i]){
            int pre=up.top(); up.pop();
            saya::modify(1, 1, s, up.top()+1, pre, b[pre]);
        }
        saya::modify(1, 1, s, up.top()+1, i, -b[i]);
        up.push(i);

        /** maintain the maximum value */
        while(down.top() && b[down.top()]<b[i]){
            int pre=down.top(); down.pop();
            saya::modify(1, 1, s, down.top()+1, pre, -b[pre]);
        }
        saya::modify(1, 1, s, down.top()+1, i, b[i]);
        down.push(i);

        /** DEBUG */
        // printf("When i == %d, the segment tre:>\n", i);
        // saya::check(1, 1, s);

        /** get the legal left endpos */
        int ret=saya::query(1, 1, s, k);
        // printf("When i == %d, ret == %d\n", i, ret);
        if(ret!=-1 && i-ret+1>len) len=i-ret+1, l=L+ret-1;
        // printf("When i == %d, l == %d, len == %d\n", i, l, len);
    }
    // printf("successfully quit!\n");
}

signed main(){
    input();
    if(d==0) return special::launch();
    int lst=1;
    rep(i, 2, n+1) if(a[i]%d!=a[lst]%d || i==n+1)
        solve(lst, i-1), lst=i;
    writc(l, ' '), writc(l+len-1, '\n');
    return 0;
}

肆、關鍵之處 ¶

真不知道該怎麼說,但是這種移動右端點,維護左端點真該學一下,同時使用單調棧維護最大最小值。