Closest Equals(代码源打卡day.12)

随便说点

鸽了好几天之后继续更新每日一题。今天的是离线线段树,区间修改单点查询,当然也有大佬有很多奇奇怪怪的做法,考虑到我比较菜就不多瞎逼逼了。

今天写这篇题解主要还是要纪念一下我一次使用 t a g tag tag来维护线段树,一个下午写了一道板子,晚上重构代码,最后还叫大佬帮我对拍,过程不可谓不艰难,不过好在最后还是找到了 b u g bug bug写完了。

题意

给出一个序列 a a a询问区间内最接近的两个相同的数字之间的距离是多少。

题解

如果是采用最暴力的方法肯定是直接遍历每一次询问,然后暴力找出最近的相同的数字,如果稍微优化一下就是记录下每个数字左边最近的与他相同的数字距离它的位置,然后遍历区间内每个值,如果左边最近的值也在这个区间内我们就可以用它来更新答案,然而这样还是不够。

我们需要的是 O ( n ) O(n) O(n)或者 O ( n l o g n ) O(nlogn) O(nlogn)的算法,也就是说只要一次遍历我就能输出结果,当然这一次遍历它的遍历顺序是不一样的。

我们这样设想一种遍历方式。我们枚举它的右端点,如果它前面已经存在了和它相等的数字,那么就是说所有包含这两个数字的区间的最小值小于等于这个距离,于是我们就可以用现在这个距离去更新前面的最小值,代表这个位置到我们当前枚举到的右端点这个区间的最小值,而这个过程我们可以用线段树来维护。

那么这种遍历方式有什么用呢?当我们更新完最小值之后,我们可以枚举以当前位置为右端点的左端点,然后通过线段树询问获取当前节点的最小值。

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
#define inf 0x3f3f3f3f
#define lc(p) p << 1
#define rc(p) p << 1 | 1
const int N=5e5+10;
int a[N];
int main(){

    int n, m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i ++) scanf("%d",a+i);

    vector<int> tree((n << 2) + 10);
    auto build = [&](auto self, int l, int r, int p) -> void {
        tree[p] = inf;
        if(l == r) return ;
        int mid = (l + r) >> 1;
        self(self, l, mid, lc(p));
        self(self, mid + 1, r, rc(p));
    };
    build(build, 1, n, 1);

    vector< vector<pair<int,int>> > g(n + 1);
    for(int i = 1;i <= m;i ++){
        int l, r;
        scanf("%d%d",&l,&r);
        g[r].push_back({l, i});
    }

    vector<int> tag((n << 2) + 10);
    auto f = [&](int x, int k) -> void {
        tag[x] = (tag[x] == 0 ? k : min(tag[x], k));
        tree[x] = min(tree[x], k);
        return ;
    };
    auto push_down = [&](int x) -> void {
        if(tag[x] == 0) return;
        f(lc(x), tag[x]);
        f(rc(x), tag[x]);
        tag[x] = 0;
        return ;
    };
    auto change = [&](auto self, int l, int r, int p, int x, int y, int mini) -> void {
        //当前左边界 当前右边界 当前节点 目标左边界 目标右边界 修改目标
        if(l > y || r < x) return;
        if(x <= l && r <= y){
            tree[p] = min(tree[p], mini);
            tag[p] = (tag[p] == 0 ? mini : min(tag[p], mini));
            return ;
        }
        push_down(p);
        int mid = (l + r) >> 1;
        self(self, l, mid, lc(p), x, y, mini);
        self(self, mid + 1, r, rc(p), x, y, mini);
        tree[p] = min(tree[lc(p)], tree[rc(p)]);
        return ;
    };
    auto query = [&](auto self, int l, int r, int p, int x) -> int {
        if(l == r) return tree[p];
        push_down(p);
        int mid = (l + r) >> 1;
        if(x <= mid) return self(self, l, mid, lc(p), x);
        else return self(self, mid + 1, r, rc(p), x);
    };

    map<int,int> mp;
    vector<int> ans(m + 1);
    for(int i = 1;i <= n;i ++){
        if(mp[a[i]]) change(change, 1, n, 1, 1, mp[a[i]], i - mp[a[i]]);
        mp[a[i]] = i;
        for(auto v : g[i]) ans[v.second] = query(query, 1, n, 1, v.first);
    }
    for(int i = 1;i <= m;i ++) printf("%d\n",ans[i] == inf ? -1 : ans[i]);
 
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值