P7907 [Ynoi2005] rmscne 题解

P7907 [Ynoi2005] rmscne 题解

退役前的最后一篇题解,献给 Ynoi。再见了各位。

题目大意

给定一个长度为 n 的序列和 m 次查询,对于每次查询,给定 l,r,求出一个最短的子区间 [l,r],满足所有在区间 [l,r] 中出现的数也在 [l,r] 中出现过。

题解

题还是很好的。我讲的尽量细一点。

由于题解里所述的 q 与原题面重了,所以我们将原题面里的 q 次查询改成 m 次查询。

1n,m2×106,3s,512MB

先设一个东西:

Sl,r 表示区间 [l,r] 里所有的数构成的集合。那么原题目的限制条件就可以转化为 Sl,r=Sl,r

没说强制在线,然后还是区间查询,那就考虑离线扫描线。

每扫到一个位置,处理右端点在这个位置的所有查询。

先来考虑简化版的问题:

给你一个区间 [l,r] ,查询最短的满足 Sl,r=Sl,r 的子区间 [l,r] 的长度。

对于这个简化版问题,我们可以对每一个位置,维护左端点 l 在这个位置时,最小的右端点 r 以及其区间长度。但是我们发现,以某一些位置为左端点时,找不到对应的右端点,我们现在设法除去这些区间的干扰,不把它们算在答案的考虑范围之内,这方便我们一会儿解决原问题。设 [q,r] 是以 rr 时,最短的满足条件的子区间。不难发现,将 i(i(q,r]) 作为左端点 l 时,是没有对应的右端点 r 的。所以这个简化版问题的答案就是左端点为 i(i[l,q]) 时,最短的区间长度。这个东西很线段树,不是么?

我们再来考虑原问题。

线段树维护对于一个点 i,满足 Si,p=Si,rr 为当前扫到的位置)的最小的 p。但是我们不需要这个 p,我们只需要维护 pi+1,即区间长度。

那么我们在处理区间 [l,r] 的答案时,就是线段树上 [l,q] 的最小值(q 就是上文所述的东西,即 [q,r] 是以 rr 时,最短的满足条件的子区间)。

考虑这样求答案的正确性。

根据 q 的定义,Sq,r=Sl,r,所以限制条件就可以转化成 Sl,r=Sl,r=Sq,r。对于区间 [l,q] 里的每一个 iSi,r=Sq,r 这个条件一定满足,而对于区间 (q,r] 里的每一个 iSi,r=Sq,r 这个条件一定不满足(因为在位置 q 上的数,一定在 Sq,r 仅出现过一次,否则不优,q 可以更大)。

证毕。

所以我们现在的问题就转化成了,对于每一个 r,求出对应的 q,然后直接在线段树上查询即可。

查询就没了,现在讲修改。

考虑现在新扫到了一个位置 r+1,这个位置上的数为 ar+1,我们记它上一次出现的位置为 lst,那么对于区间 [1,lst] 里的每一个 iSi,r=Si,r+1,不需要进行什么修改,而对于区间 [lst+1,r+1] 里的每一个 iSi,rSi,r+1,所以满足 Si,p=Si,r 的最小的 p 一定需要更新成 r+1

所以线段树部分就没了,就是一颗支持区间修改,区间最值查询的线段树,比较平凡。

我们考虑现在的复杂度是什么。

一共会进行 n 次修改,每次修改 O(logn);一共有 m 次查询,每次查询需要找到一个 q(现在我们先二分找到这个 q,一会儿再优化),然后再在线段树上查询,单次 O(logn)。总的复杂度是 O(nlogn),但是常数似乎不小,过不去。

考虑优化。

考虑怎么对于每一个 r 迅速找到一个 q

一个不太好想的优化,反正我没想到,看题解想到的。

考虑并查集优化,对于一个位置 i,它的祖先就是,如果以这个点作为 q,那么更优的 q 的位置,或者说我们先假定 iq,但是我们发现这个位置不优,于是它的祖先就指向了一个更优的位置。每一次新扫到一个位置 r+1,这个位置上的数为 ar+1,我们记它上一次出现的位置为 lst,那么我们将位置 lst 的祖先更新成 lst+1 即可。我们最终用的 q 就是 l 位置的祖先。

为什么这么更新?

因为如果 q 在位置 lst 上,当前数与位置 lst 上的数相同,所以 lst 位置就没有用了,所以 lst+1 就比 lst 更优了。

然后这道题基本就结束了。

整理一下:

  1. 离线扫描线;

  2. 每扫到一个点,线段树上区间修改,然后更新并查集;

  3. 处理右端点再当前位置的查询,每个查询的 q 就是 l 的祖先。

代码

#include <bits/stdc++.h>
#define P pair<int, int>
#define MP make_pair
using namespace std;

const int M = 2000005;
int n, q, a[M], ans[M], fa[M], lst[M];
int minn[M << 2], lazy[M << 2];
vector<P> vec[M];

inline int findf(int x) {
    while(x != fa[x]) 
        x = fa[x] = fa[fa[x]];
    return x;
}

inline void push_down(int rt, int l, int r) {
    if(lazy[rt]) {
        int mid = (l + r) >> 1, ls = rt << 1, rs = ls | 1;
        minn[ls] = lazy[rt] - mid + 1;
        minn[rs] = lazy[rt] - r + 1;
        lazy[ls] = lazy[rt];
        lazy[rs] = lazy[rt];
        lazy[rt] = 0;
    }
}

inline void push_up(int rt) {
    int ls = rt << 1, rs = ls | 1;
    minn[rt] = min(minn[ls], minn[rs]);
}

void build(int rt, int l, int r) {
    minn[rt] = INT_MAX;
    lazy[rt] = 0;
    if(l == r) 
        return;
    int mid = (l + r) >> 1, ls = rt << 1, rs = ls | 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
}

void change(int rt, int l, int r, int zuo, int you, int addend) {
    if(zuo <= l && r <= you) {
        minn[rt] = addend - r + 1;
        lazy[rt] = addend;
        return;
    }
    push_down(rt, l, r);
    int mid = (l + r) >> 1, ls = rt << 1, rs = ls | 1;
    if(zuo <= mid)
        change(ls, l, mid, zuo, you, addend);
    if(you > mid)
        change(rs, mid + 1, r, zuo, you, addend);
    push_up(rt);
}

int ask(int rt, int l, int r, int zuo, int you) {
    if(zuo <= l && r <= you) 
        return minn[rt];
    push_down(rt, l, r);
    int mid = (l + r) >> 1, ls = rt << 1, rs = ls | 1, res = INT_MAX;
    if(zuo <= mid)
        res = ask(ls, l, mid, zuo, you);
    if(you > mid)
        res = min(res, ask(rs, mid + 1, r, zuo, you));
    push_up(rt);
    return res;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n;
    build(1, 1, n);
    for(int i = 1; i <= n; ++ i) 
        cin >> a[i], fa[i] = i;
    cin >> q;
    for(int i = 1; i <= q; ++ i) {
        int l, r;
        cin >> l >> r;
        vec[r].push_back(MP(l, i));
    }
    for(int i = 1; i <= n; ++ i) {
        fa[lst[a[i]]] = lst[a[i]] + 1;
        change(1, 1, n, lst[a[i]] + 1, i, i);
        for(int j = 0; j < vec[i].size(); ++ j) 
            ans[vec[i][j].second] = ask(1, 1, n, vec[i][j].first, fa[findf(vec[i][j].first)]);
        lst[a[i]] = i;
    }
    for(int i = 1; i <= q; ++ i) 
        cout << ans[i] << '\n';
}
posted @   流星Meteor  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示