力扣周赛 | 力扣第409场周赛

本文将对力扣第409场周赛的T3和T4进行讲解:新增道路查询后的最短距离II、交替组III,涉及最短路、广度优先搜索、树状数组等知识点。

T3 - 新增道路查询后的最短距离 II

题目链接:100376. 新增道路查询后的最短距离 II

题目描述

给你一个整数 n n n 和一个二维整数数组 q u e r i e s queries queries

n n n 个城市,编号从 0 0 0 n − 1 n - 1 n1。初始时,每个城市 i i i 都有一条单向道路通往城市 i + 1 i + 1 i+1 0 < = i < n − 1 0 <= i < n - 1 0<=i<n1)。

q u e r i e s [ i ] = [ u i , v i ] queries[i] = [ui, vi] queries[i]=[ui,vi] 表示新建一条从城市 u i ui ui 到城市 v i vi vi单向道路。每次查询后,你需要找到从城市 0 0 0 到城市 n − 1 n - 1 n1最短路径长度

所有查询中不会存在两个查询都满足 q u e r i e s [ i ] [ 0 ] < q u e r i e s [ j ] [ 0 ] < q u e r i e s [ i ] [ 1 ] < q u e r i e s [ j ] [ 1 ] queries[i][0] < queries[j][0] < queries[i][1] < queries[j][1] queries[i][0]<queries[j][0]<queries[i][1]<queries[j][1]

返回一个数组 a n s w e r answer answer,对于范围 [ 0 , q u e r i e s . l e n g t h − 1 ] [0, queries.length - 1] [0,queries.length1] 中的每个 i i i a n s w e r [ i ] answer[i] answer[i] 是处理完 i + 1 i + 1 i+1 个查询后,从城市 0 0 0 到城市 n − 1 n - 1 n1 的最短路径的长度

数据范围
  • 3 < = n < = 105 3 <= n <= 105 3<=n<=105
  • 1 < = q u e r i e s . l e n g t h < = 105 1 <= queries.length <= 105 1<=queries.length<=105
  • q u e r i e s [ i ] . l e n g t h = = 2 queries[i].length == 2 queries[i].length==2
  • 0 < = q u e r i e s [ i ] [ 0 ] < q u e r i e s [ i ] [ 1 ] < n 0 <= queries[i][0] < queries[i][1] < n 0<=queries[i][0]<queries[i][1]<n
  • 1 < q u e r i e s [ i ] [ 1 ] − q u e r i e s [ i ] [ 0 ] 1 < queries[i][1] - queries[i][0] 1<queries[i][1]queries[i][0]
  • 查询中不存在重复的道路。
  • 不存在两个查询都满足 i ! = j i != j i!=j q u e r i e s [ i ] [ 0 ] < q u e r i e s [ j ] [ 0 ] < q u e r i e s [ i ] [ 1 ] < q u e r i e s [ j ] [ 1 ] queries[i][0] < queries[j][0] < queries[i][1] < queries[j][1] queries[i][0]<queries[j][0]<queries[i][1]<queries[j][1]
解题思路

数据范围中的最后一个条件实则在提醒,区间是可以很容易合并的,因为不存在严格相交的两个区间。

初始有 [ 0 , 1 ] , [ 1 , 2 ] , . . . , [ n − 2 , n − 1 ] [0,1],[1,2],...,[n-2,n-1] [0,1],[1,2],...,[n2,n1] n n n 个区间,将这些区间加入区间集合 s s s。依次取出每个询问 ( u , v ) (u,v) (u,v)

  • s s s 中存在位于 [ u , v ] [u,v] [u,v] 之间的区间,则找出这样的区间,将其移出集合 s s s,然后合并这些区间,合并结果即为 [ u , v ] [u,v] [u,v],将区间 [ u , v ] [u,v] [u,v] 加入集合 s s s。此时集合大小即为最短路径的长度。
  • s s s 中不存在位于 [ u , v ] [u,v] [u,v] 之间的区间,说明区间 ( u , v ) (u,v) (u,v) 必定被 s s s 中某个区间包含,也即,询问 ( u , v ) (u,v) (u,v) 不影响最短路径的长度,此时无需进行任何操作,集合大小即为最短路径的长度。

考虑到初始时,区间是完整的,即,集合 s s s 中所有区间的和为 [ , n − 1 ] [,n-1] [,n1],所以,无论如何合并,在任意时刻,区间之和都是完整的。因此,可使用链表来维护所有区间,每个节点表示一个区间端点。

vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>> &queries) {
    int nxt[n];
    // 初始区间链
    for (int i = 0; i < n; i++)nxt[i] = i + 1;
    // 目前的区间数量
    int last = n - 1;
    vector<int> res;
    for (auto &q: queries) {
        int u = q[0], v = q[1];
        // 存在区间位于区间[u,v]之内
        if (nxt[u] != -1 && nxt[v] != -1) {
            for (int i = u, t; i != v;) {
                // nxt[i]=-1 标记i不再作为区间端点
                // last-- 表示区间[i,nxt[i]]被移出
                t = nxt[i], nxt[i] = -1, i = t, last--;
            }
            // nxt[u]=v 表示将合并得到的区间[u,v]加入区间链
            // last++ 表示[u,v]加入区间链
            nxt[u] = v, last++;
        }
        // 目前的区间数量即为询问后的最短路径的长度
        res.push_back(last);
    }
    return res;
}

时间复杂度: O ( n + q ) O(n+q) O(n+q)

空间复杂度: O ( n ) O(n) O(n),返回值不计。

T4 - 交替组 III

题目链接:100388. 交替组 III

题目描述

给你一个整数数组 c o l o r s colors colors 和一个二维整数数组 q u e r i e s queries queries c o l o r s colors colors表示一个由红色和蓝色瓷砖组成的环,第 i i i 块瓷砖的颜色为 c o l o r s [ i ] colors[i] colors[i]

  • c o l o r s [ i ] = = 0 colors[i] == 0 colors[i]==0 表示第 i i i 块瓷砖的颜色是 红色
  • c o l o r s [ i ] = = 1 colors[i] == 1 colors[i]==1 表示第 i i i 块瓷砖的颜色是 蓝色

环中连续若干块瓷砖的颜色如果是 交替 颜色(也就是说这组瓷砖中除了第一块和最后一块瓷砖以外,中间瓷砖的颜色与它 左边右边 的颜色都不同),那么它被称为一个 交替组

你需要处理两种类型的查询:

  • q u e r i e s [ i ] = [ 1 , s i z e i ] queries[i] = [1, sizei] queries[i]=[1,sizei],确定大小为 s i z e i sizei sizei交替组 的数量。
  • q u e r i e s [ i ] = [ 2 , i n d e x i , c o l o r i ] queries[i] = [2, indexi, colori] queries[i]=[2,indexi,colori],将 c o l o r s [ i n d e x i ] colors[indexi] colors[indexi]更改为 c o l o r i colori colori

返回数组 a n s w e r answer answer,数组中按顺序包含第一种类型查询的结果。

注意 ,由于 c o l o r s colors colors 表示一个 第一块 瓷砖和 最后一块 瓷砖是相邻的。

数据范围
  • 4 < = c o l o r s . l e n g t h < = 5 ∗ 104 4 <= colors.length <= 5 * 104 4<=colors.length<=5104
  • 0 < = c o l o r s [ i ] < = 1 0 <= colors[i] <= 1 0<=colors[i]<=1
  • 1 < = q u e r i e s . l e n g t h < = 5 ∗ 104 1 <= queries.length <= 5 * 104 1<=queries.length<=5104
  • q u e r i e s [ i ] [ 0 ] = = 1 queries[i][0] == 1 queries[i][0]==1 q u e r i e s [ i ] [ 0 ] = = 2 queries[i][0] == 2 queries[i][0]==2
  • 对于所有的 i i i
    • q u e r i e s [ i ] [ 0 ] = = 1 queries[i][0] == 1 queries[i][0]==1 q u e r i e s [ i ] . l e n g t h = = 2 queries[i].length == 2 queries[i].length==2, 3 < = q u e r i e s [ i ] [ 1 ] < = c o l o r s . l e n g t h − 1 3 <= queries[i][1] <= colors.length - 1 3<=queries[i][1]<=colors.length1
    • q u e r i e s [ i ] [ 0 ] = = 2 queries[i][0] == 2 queries[i][0]==2 q u e r i e s [ i ] . l e n g t h = = 3 queries[i].length == 3 queries[i].length==3, 0 < = q u e r i e s [ i ] [ 1 ] < = c o l o r s . l e n g t h − 1 0 <= queries[i][1] <= colors.length - 1 0<=queries[i][1]<=colors.length1, 0 < = q u e r i e s [ i ] [ 2 ] < = 1 0 <= queries[i][2] <= 1 0<=queries[i][2]<=1
解题思路

考虑长度为 a a a 的交替组,若 b ≤ a b≤a ba,则共包含 a − b + 1 a-b+1 ab+1 个长度为 b b b 的交替组。

将换划分为若干个交替组,要求交替组数量尽可能少。令 f [ x ] f[x] f[x] = 长度大于等于 x x x 的交替组的长度之和, g [ x ] g[x] g[x] = 长度大于等于 x x x 的交替组的数量,则,长度为 s s s 的交替组的数量为 f [ x ] − ( s − 1 ) ∗ g [ x ] f[x]-(s-1)*g[x] f[x](s1)g[x]

通常情况下,“长度大于等于 x x x 的交替组的长度之和”会转换为“长度总和”-“长度小于等于 x − 1 x-1 x1 的交替组的长度之和”, g [ x ] g[x] g[x] 同理。

“长度小于等于 x x x 的交替组的长度之和”可用树状数组维护,“长度小于等于 x x x 的交替组的数量”同理。

对于修改操作,要么会导致交替组合并,要么会导致交替组拆分,所以需要维护交替组集合。由于所有交替组首尾相连可以组成环,所以,只需要记录每个交替组的右端点即可(左端点也可)。

综上,每次修改操作,先对交替组进行合并或者拆分,在根据交替组处理结果对树状数组进行修改,修改操作大体思路如下:

  • 先通过 i s t ( ) / d e l ( ) ist()/del() ist()/del() 对交替组进行拆分/合并;
  • i s t ( ) / d e l ( ) ist()/del() ist()/del() 中调用中间函数 u p d a t e ( ) update() update() 对树状数组进行更新;
  • u p d a t e ( ) update() update() 实际调用树状数组模板函数 a d d ( ) add() add() 对树状数组进行更新。
代码实现
vector<int> numberOfAlternatingGroups(vector<int> &colors, vector<vector<int>> &queries) {

    int n = colors.size();

    // tr1[i] 表示长度小于或等于i的交替组的数量
    // tr2[i] 表示长度小于或等于i的交替组的长度之和
    int tr1[n + 1], tr2[n + 1];
    memset(tr1, 0, sizeof tr1);
    memset(tr2, 0, sizeof tr2);

    // 树状数组模板代码
    auto add = [&](int *tr, int p, int v) {
        for (; p <= n; p += p & -p)tr[p] += v;
    };
    auto query = [&](int *tr, int p) {
        int res = 0;
        for (; p; p -= p & -p)res += tr[p];
        return res;
    };

    /**
     * v=1 表示新增,v=-1 表示删除
     * 新增右端点p,即,将[l+1,r]拆分为[l+1,p]和[p+1,r]两个交替组
     * 或者
     * 删除右端点p,即,将[l+1,p]和[p+1,r]两个交替组合并为[l+1,r]
     */
    auto update = [&](int l, int r, int p, int v) {

        // v=1时删除[l+1,r]
        // v=-1时新增[l+1,r]
        int len = (r - l + n) % n;
        // 当仅有一个右端点且新增右端点p时,l和r相等,len为0
        if (len == 0)len = n;
        add(tr1, len, -v), add(tr2, len, -v * len);

        // v=1时新增[l+1,p]和[p+1,r]
        // v=-1时删除[l+1,p]和[p+1,r]

        // 右端点不重复,故p和l必定不相等,故len必不为0
        len = (p - l + n) % n;
        add(tr1, len, v), add(tr2, len, v * len);

        // 右端点不重复,故p和r必定不相等,故len必不为0
        len = (r - p + n) % n;
        add(tr1, len, v), add(tr2, len, v * len);

    };

    // 存储交替组右端点
    set<int> st;

    // 往st中新增交替组右端点p
    auto ist = [&](int p) {
        if (st.empty()) {
            // 在无右端点的情况下新增一个右端点
            // 相当于新增1个长度为n的交替组,长度小于等于n的交替组的总和加n
            // 由于此时将端点p添加到st中后,p的前后均没有右端点,故无法走else中的逻辑,需要特殊处理
            st.insert(p);
            add(tr1, n, 1), add(tr2, n, n);
        } else {
            // 在有右端点的情况下新增一个右端点
            // 相当于将一个交替组拆分成两个交替组
            // 即将[l+1,r]拆分为[l+1,p]和[p+1,r]两个交替组
            // it指向右端点p
            auto it = st.insert(p).first;
            int l = it == st.begin() ? *prev(st.end()) : *prev(it);
            int r = next(it) == st.end() ? *st.begin() : *next(it);
            update(l, r, p, 1);
        }
    };
    // 从st中删除交替组右端点p
    auto del = [&](int p) {
        if (st.size() == 1) {
            // 在仅有1个右端点的情况下删除一个右端点
            // 相当于删除1个长度为n的交替组,长度小于等于n的交替组的总和减n
            // 由于此时将端点p从st中删除后,st中没有右端点,故无法走else中的逻辑,需要特殊处理
            st.erase(st.find(p));
            add(tr1, n, -1), add(tr2, n, -n);
        } else {
            // 在有多个右端点的情况下删除一个右端点
            // 相当于将两个交替组合并为一个交替组
            // 即将[l+1,p]和[p+1,r]两个交替组合并为[l+1,r]
            // it指向右端点p的下一个元素
            auto it = st.erase(st.find(p));
            int l = it == st.begin() ? *prev(st.end()) : *prev(it);
            int r = it == st.end() ? *st.begin() : *it;
            update(l, r, p, -1);
        }
    };

    // 记录每个最长交替组的右端点
    for (int i = 0; i < n; i++)
        if (colors[i] == colors[(i + 1) % n])ist(i);

    // 处理每个请求
    vector<int> res;
    for (auto &q: queries) {
        if (q[0] == 1) {
            // 查询
            if (!st.empty()) {
                // 长度大于q[1]的交替组的数量=所有交替组的数量-长度小于等于q[1]-1的交替组的数量
                int cnt = query(tr1, n) - query(tr1, q[1] - 1);
                // 长度大于q[1]的交替组的总和=所有交替组的总和-长度小于等于q[1]-1的交替组的总和
                int total = query(tr2, n) - query(tr2, q[1] - 1);
                res.push_back(total - (q[1] - 1) * cnt);
            }
                // 无右端点的情况并未记录到线段树,故特殊处理
            else res.push_back(n);
        } else {
            // 修改
            int p = q[1], c = q[2];
            // p的前后位置
            int pre = (p - 1 + n) % n, nxt = (p + 1) % n;
            // 改前pre和p一样,则先删除右端点pre
            if (colors[pre] == colors[p])del(pre);
            // 改前p和nxt一样,则先删除右端点p
            if (colors[p] == colors[nxt])del(p);
            // 修改p的颜色
            colors[p] = c;
            // 改后pre和p一样,则新增右端点pre
            if (colors[pre] == colors[p])ist(pre);
            // 改后p和nxt一样,则新增右端点p
            if (colors[p] == colors[nxt])ist(p);
            // 需要注意的是,改前pre和p一样,改后pre和p可能依旧一样(如改),所以,改前改后得分开处理
            // p和nxt同理
        }
    }
    return res;
}

时间复杂度: O ( ( n + q ) l o g ( n ) ) O((n+q)log(n)) O((n+q)log(n))

空间复杂度: O ( n ) O(n) O(n),返回值不计。

END

以上就是本文的全部内容,如果觉得有一点点帮助,欢迎点赞、转发加关注,如果有任何问题,欢迎在评论区交流和讨论,咱们下期见!

文章声明:题目来源 力扣 平台,如有侵权,请联系删除!

题目来源:力扣第409场双周赛

文章文档:公众号 字节幺零二四 回复关键字可获取本文文档。

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值