[Niuke2021-I] Interval Query [回滚莫队/可撤销并查集]

Author Avatar
空気浮遊 8月1日
  • 在其它设备中阅读本文章

跑的还挺快。

Problem

https://ac.nowcoder.com/acm/contest/11256/I

Solution

回滚莫队的特点在于把“删点”变成了“撤销”。也就是变成了“按顺序删按顺序加入的点”。

这么说来其实没有太大的本质区别,只是给“删点”加了特殊条件而已。

但只要这么干了就可以用可撤销并查集了。

听说std用的是双向链表,我不会。我想想其实并查集跟我想象的双向链表差别不大的样子。


https://www.cnblogs.com/Parsnip/p/10969989.html

对于左端点处在同一个块里的询问,右端点升序排序。每次将莫队左端点重置在块的最右侧,右端点因为是升序的无需重置。

重置的过程就是撤销。

如果右端点一开始是在块的最右侧的左侧,说明这个询问可以暴力查询。

复杂度反正是$O(n\sqrt{n})$的,似乎也不像莫队了。应该算是另一种根号区间处理问题的方式吧。

最后用可撤销并查集维护一下深度、子树大小就行。说是并查集可撤销其实要撤销的不只是并查集,这一点需要稍微注意一下。

撤销方式就是很裸的往stack插入你的修改记录。


顺带一提由于用了可撤销并查集本题时间复杂度上界应该是$O(n\sqrt{n}\log n + k\log n)$的。

本题还有一些无聊且容易让人误解的限制条件。

Code

#include<bits/stdc++.h>
#define MOD (998244353)
using namespace std;
// head
template<typename T>inline void gn(T &x) {
    char ch=getchar(), pl=1; x=0;
    while(!isdigit(ch)) pl=(ch=='-'?-1:1), ch=getchar();
    while(isdigit(ch)) x=x*10+ch-'0', ch=getchar();
    x*=pl;
}
typedef long long ll;
const int N=1e5+10, M=N<<2;
struct Query {
    int l, r, k, id;
} qu[N];
int a[N];

// union-find
int fa[N], rk[N], sz[N];
int Find(int x) {
    return fa[x]?Find(fa[x]):x;
}
stack<int> ca, cb, ra, rb, cc;
void Union(int a, int b, bool w) {
    int af=Find(a), bf=Find(b);
    if(af==bf) return;
    if(rk[af]>rk[bf]) swap(af, bf);
    if(w) {
        ca.push(af), cb.push(bf), ra.push(rk[af]), rb.push(rk[bf]);
    }
    sz[bf]+=sz[af];
    fa[af]=bf;
    rk[bf]=max(rk[bf], rk[af]+1);
}

// mo
int n, q;
int Be[N], Ber[N], unit;
int cnt[N];
ll ans[N];
int nans;
bool cmp(const Query &a, const Query &b) {
    return Be[a.l]==Be[b.l]?a.r<b.r:a.l<b.l;
}
void Add(int _x, bool w) {
    int x=a[_x];
    cnt[x]++;
    if(w) cc.push(x);
    if(cnt[x-1]) Union(x, x-1, w);
    if(cnt[x+1]) Union(x, x+1, w);
    nans=max(nans, sz[Find(x)]);
}
void Widr() {
    while(!ca.empty()) {
        rk[ca.top()]=ra.top();
        rk[cb.top()]=rb.top();
        fa[ca.top()]=0;
        sz[cb.top()]-=sz[ca.top()];
        ca.pop(), cb.pop(), ra.pop(), rb.pop();
    }
    while(!cc.empty()) cnt[cc.top()]--, cc.pop();
}

// pre
int ppo[N];

int main() {
    gn(n), gn(q);
    ppo[0]=1;
    for(int i=1; i<N; i++)
        ppo[i]=1ll*ppo[i-1]*(n+1)%MOD;
    int unit = sqrt(n);
    for(int i=1; i<=n; i++) Be[i]=i/unit, Ber[Be[i]]=i;
    for(int i=1; i<=n; i++)
        scanf("%d", a+i);
    for(int i=1; i<=q; i++) {
        gn(qu[i].l), gn(qu[i].r), gn(qu[i].k);
        qu[i].id=i;
    }
    sort(qu+1, qu+1+q, cmp);
    int xl, xr;
    int rans;
    int nl, nr;
    for(int i=1; i<=q; i++) {
        nl=qu[i].l, nr=qu[i].r;
        if(Be[nl]!=Be[qu[i-1].l] || i==1) {
            xr=Ber[Be[nl]];
            memset(fa, 0, sizeof(fa));
            memset(rk, 0, sizeof(rk));
            for(int i=1; i<=n; i++) sz[i]=1, cnt[i]=0;
            nans=1;
        }
        if(nr<Ber[Be[nl]]) {
            rans=nans;
            xl=nl;
            xr=nl-1;
            while(xr<nr) Add(++xr, true);
            for(int j=0; j<qu[i].k; j++) {
                ans[qu[i].id]+=1ll*nans*ppo[j]%MOD;
                if(j<qu[i].k-1) Add(--xl, true), Add(++xr, true);
            }
            nans=rans;
            Widr();
            xr=Ber[Be[nl]];
            continue;
        }
        xl=Ber[Be[nl]]+1;
        while(xr<nr) Add(++xr, false);
        rans=nans;
        while(xl>nl) Add(--xl, true);
        for(int j=0; j<qu[i].k; j++) {
            ans[qu[i].id]+=1ll*nans*ppo[j]%MOD;
            if(j<qu[i].k-1) Add(--xl, true), Add(++xr, true);
        }
        nans=rans;
        xr=nr;
        Widr();
    }
    for(int i=1; i<=q; i++)
        printf("%lld\n", ans[i]%MOD);
    return 0;
}

本文链接:https://acxblog.site/archives/niuke2021-i.html
博客以 CC BY-NC-SA 4.0 协议进行许可。