20200706D

题目点击这里

题目大意

给定一个长度为 n n n 的序列 a i a_i ai,共有 m m m 个询问,每次询问包含一个区间 l , r l, r l,r,求出满足 x , y ∈ [ l , r ] , a x = a y x,y \in [l, r], a_x = a_y x,y[l,r],ax=ay 的所有数对 ( x , y ) (x, y) (x,y) ∣ x − y ∣ |x - y| xy 的最小值,即相等的两个元素之间的最小距离。

解法

不难想到对于 a x a_x ax 来说只有上一个和他相同的元素或者下一个和他相同的元素才有可能对答案造成贡献,所以我们不妨将这个询问转化为区间包含问题,即:

假设当前序列为 3 , 5 , 3 , 6 , 2 , 3 3,5,3,6,2,3 3,5,3,6,2,3,那么考虑所有为 3 3 3 的元素可能对答案的贡献,我们可以将其转化为两个区间 [ 1 , 3 ] [1, 3] [1,3] [ 3 , 6 ] [3, 6] [3,6],一个区间会对所有包含他的询问造成区间长度的贡献。

那么我们就可以将问题转化为给定若干个区间,每次询问同样给定一个区间 [ l , r ] [l, r] [l,r],求所有被 [ l , r ] [l, r] [l,r] 包含的区间中长度最小的区间。

不难发现区间个数是 O ( n ) O(n) O(n) 的。

首先考虑如何求出所有的区间,我们可以枚举区间右端点位置 r r r,那么对应的左区间端点位置就是序列中在 a r a_r ar 之前的第一个与 a r a_r ar 相等的元素的位置,这里可以用一个 m a p map map 求得左端点。

比如序列 3 , 5 , 3 , 5 , 2 , 3 , 2 3, 5, 3, 5, 2, 3, 2 3,5,3,5,2,3,2 最终求得的区间为 [ 1 , 3 ] , [ 2 , 4 ] , [ 3 , 6 ] , [ 5 , 7 ] [1, 3], [2, 4], [3, 6], [5, 7] [1,3],[2,4],[3,6],[5,7]

那么接下来考虑如何处理询问,即对于一个询问 [ l , r ] [l, r] [l,r],求出其所有包含的区间长度最短的那个。

不妨考虑离线做法。

首先我们当前处理的询问区间右端点 r r r r r r 1 1 1 枚举到 n n n,然后维护一个区间集合,维护所有右端点小于 r r r 的区间,那么对于询问的每一个左端点 l l l,我们只要在当前集合中寻找左端点大于 l l l 的区间中长度最小的就可以了。

这个集合可以用线段树来维护,创建一个维护最大值的线段树,每次改变 r r r 就更新对应 l l l 的长度,查询时用线段树直接查询即可。

最终复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include <bits/stdc++.h>

std::map<int, int> map;

const int MAXN = 5e5 + 5;

struct Sgt {
    Sgt *lc, *rc;
    int l, r, min;

    Sgt(int l, int r, Sgt *lc, Sgt *rc) : l(l), r(r), lc(lc), rc(rc), min(0x7fffffff) {}

    static Sgt *build(int l, int r) {
        int mid = l + r >> 1;
        return l == r ? new Sgt(l, r, 0, 0) : new Sgt(l, r, build(l, mid), build(mid + 1, r));
    }

    void modify(int x, int v) {
        if(l == x && x == r) min = std::min(v, min);
        else if(x < l || x > r) return;
        else lc->modify(x, v), rc->modify(x, v), min = std::min(lc->min, rc->min);
    }

    int query(int al, int ar) {
        if(al <= l && r <= ar) return min;
        else if(ar < l || al > r) return 0x7fffffff;
        else return std::min(lc->query(al, ar), rc->query(al, ar));
    }
} *sgt;

int a[MAXN], n, m, ans[MAXN], last[MAXN];

struct Q {
    int l, r, id;
} q[MAXN];

std::vector<Q> ql[MAXN];

int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", a + i);
    sgt = Sgt::build(1, n);
    for(int i = 1; i <= m; i++) {
        int l, r;
        scanf("%d%d", &l, &r);
        q[i].l = l;
        q[i].r = r;
        q[i].id = i;
    }
    for(int i = 1; i <= m; i++) {
        ql[q[i].r].push_back(q[i]);
    }
    for(int i = 1; i <= n; i++) last[i] = i;
    for(int i = 1; i <= n; i++) {
        if(map.count(a[i])) last[i] = map[a[i]];
        map[a[i]] = i;
    }
    for(int i = 1; i <= n; i++) {
        if(last[i] != i) sgt->modify(last[i], i - last[i]);
        for(Q x : ql[i]) {
            ans[x.id] = sgt->query(x.l, i);
        }
    }
    for(int i = 1; i <= m; i++) printf("%d\n", ans[i] == 0x7fffffff ? -1 : ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值