首页 > 其他分享 >浅谈线段树优化建图

浅谈线段树优化建图

时间:2023-02-01 17:36:16浏览次数:50  
标签:连边 浅谈 int 线段 建图 log 区间 dis


前置知识

线段树 建图(?)


前言

接触到线段树优化建图还是因为做 "[USACO11OPEN] Mowing the Lawn G" 不想写单调队列优化 DP 到处找其他做法翻到的(

学了过后感觉很神奇,就是能用到题目不多,没什么拿来练习的题目。


用途

用于解决对于 \(u\to v=[l,r]\texttt{ / }u=[l,r]\to v\texttt{ / } u = [l_1,r_1]\to v=[l_2,r_2]\) 连边的优化。

对于暴力时间复杂度和边数都是 \(n\texttt{ / }n\texttt{ / }n^2\) 级别的。
线段树优化复杂度和边数都将是 \(\log n\texttt{ / }\log n\texttt{ / }\log^2 n\) 级别。


方法

思想

这是一颗线段树:
graph.png

对于每个节点通常用来记录其区间 \([l,r]\) 的信息。
那让其维护的信息为到其区间内 \([l,r]\) 所有点的连边,线段树优化建图大致思想就出来了。
举个例子,对于 \(u\to v=[1,6]\),我们只需要对 \([1,4],[5,6]\) 两个部分类似于打 lazy 标记,等到后面走到这个点时继续往下走其对应儿子节点就行了。

实现

  1. 建树。
    其实就是普通的线段树的建树啦。
    只不过因为还要往儿子节点走,需要往儿子节点连一条单向边(因为这条边其实是不存在的,边权通常设为 \(0\),但有时也会视情况而变)。
    vector <pair <int, long long> > l [N << 1];
    struct seg_node {
        int l, r, ls, rs; 
    } t [N << 1];
    long long e [N];
    void add (int u, int v) {
        /*
        ...
        */
        return ;
    }
    int a [N];// a [i] 存储在树中第 i 个对应的编号
    int cnt;
    void build (int k, int l, int r) {
        t [k] .l = l;
        t [k] .r = r;
        if (l == r) {
            a [l] = k;
            return ;
        }
        int mid = (l + r) >> 1;
        t [k] .ls = ++ cnt;
        build (t [k] .ls, l, mid);
        t [k] .rs = ++ cnt;
        build (t [k] .rs, mid + 1, r);
        add (k, t [k] .ls);
        add (k, t [k] .rs);
        // 向儿子连边
        return ;
    }
    
  2. 连边。
    也就相当于普通线段树的区间操作。
    即是在当前区间 \([l,r]\) 被需要连边的区间 \([x,y]\) 包含时直接对 \([l,r]\) 连边不继续往下走儿子节点。
    易得区间个数是 \(\log n\) 级别的。
    /*
    这里实现的是单点 -> 区间
    区间 -> 单点同理
    区间 -> 区间记录第一个区间 [x1,y1] 的小区间对第二个区间 [x2,y2] 的小区间直接连边即可
    */
    void update (int k, int l, int r, int u) {
        if (r < t [k] .l || t [k] .r < l) {
           return ;
        }
        if (l <= t [k] .l && t [k] .r <= r) {
            add (u, k);// 直接连边
            return ;
        }
       update (t [k] .ls, l, r, u);
       update (t [k] .rs, l, r, u);
       return ;
    } 
    
  3. 其他操作。
    现在就可以进行最短路,Tarjan 等操作啦。

例题

Luogu P2627 [USACO11OPEN] Mowing the Lawn G

https://www.luogu.com.cn/problem/P2627

发现不能超过连续 \(k\) 个,则对于选位置 \(i\) 在 \([i+1,i+k+1]\) 必有一个位置选不了,那 \(i\to j=[i+1,i+k+1]\) 都有一条边权为 \(e_j\) 的边。
同时对起点建一个超级源点 \(0\to [1,k+1]\),对终点也建一个超级源点 \([n-k,n]\to n+1\)。
则最后跑最短路得出的答案 \(dis_{n+1}\) 就为不选的点的 \(e_i\) 和最小值,答案即为 \(\sum_{i=1}^n e_i-dis_{n+1}\)。

假设 \(n,k\) 同级,每个点边数都为 \(\log n\) 级别,总边数就为 \(n \log n\) 级别,跑 Dijkstra 时间复杂度为 \(O(n\log(n\log n))\approx O(n\log n)\),\(\log\log n\) 完全可以当作小常数来看就去掉了。

# include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
vector <pair <int, long long> > l [N << 1];
struct seg_node {
    int l, r, ls, rs; 
} t [N << 1];
long long e [N];
void add (int u, int v) {
    long long w = (t [v] .l == t [v] .r ? e [t [v] .l] : 0);// 如果是单个点记得边权为 e [t [v] .l]
    l [u] .emplace_back (v, w);
    return ;
}
int a [N];
int cnt;
void build (int k, int l, int r) {
    t [k] .l = l;
    t [k] .r = r;
    if (l == r) {
        a [l] = k;
        return ;
    }
    int mid = (l + r) >> 1;
    t [k] .ls = ++ cnt;
    build (t [k] .ls, l, mid);
    t [k] .rs = ++ cnt;
    build (t [k] .rs, mid + 1, r);
    add (k, t [k] .ls);
    add (k, t [k] .rs);
    return ;
}
void update (int k, int l, int r, int u) {
    if (r < t [k] .l || t [k] .r < l) {
        return ;
    }
    if (l <= t [k] .l && t [k] .r <= r) {
        add (u, k);
        return ;
    }
    update (t [k] .ls, l, r, u);
    update (t [k] .rs, l, r, u);
    return ;
} 
long long dis [N << 1];
bool vis [N << 1];
int main () {
    int n, k;
    scanf ("%d %d", & n, & k);
    long long tot = 0;
    for (int i = 1; i <= n; ++ i) {
        scanf ("%lld", & e [i]);
        tot += e [i];
    }
    build (++ cnt, 1, n + 1);// 建树记得加上 n + 1 这个点
    for (int i = 0; i <= n; ++ i) {
        update (1, i + 1, min (i + k + 1, n + 1), a [i]);// 注意右边界不能超过 n + 1
    }
    memset (dis, 0x3f3f3f, sizeof dis);
    dis [0] = 0;
    priority_queue <pair <long long, int> > q;
    q .emplace (0, 0);
    while (! q .empty ()) {
        pair <long long, int> top = q .top ();
        q .pop ();
        int u = top .second;
        if (vis [u]) {
            continue;
        }
        vis [u] = 1;
        for (pair <int, long long> i : l [u]) {
            int v = i .first;
            long long w = i .second;
            if (dis [u] + w < dis [v]) {
                dis [v] = dis [u] + w;
                q .emplace (- dis [v], v);
            }
        }
    }// dijkstra
    printf ("%lld", tot - dis [a [n + 1]]);
    return 0;
}

标签:连边,浅谈,int,线段,建图,log,区间,dis
From: https://www.cnblogs.com/lctj-bot/p/17083538.html

相关文章

  • 浅谈爬虫开发中几种形式的cookie的互相转换与利用
    前言在我们写爬虫的过程中,cookie一般是我们最经常接触到的东西。而由于在爬虫过程中的各个阶段的难度往往不同,所以我们很多时候会采用浏览器、requests等等各种方案来在采......
  • 浅谈搜索引擎之solr篇
    Solr安装与配置1.1什么是Solr大多数搜索引擎应用都必须具有某种搜索功能,问题是搜索功能往往是巨大的资源消耗并且它们由于沉重的数据库加载而拖垮你的应用的性能。这就是为......
  • 博奥智源公司,浅谈软件运维服务项目需求设计
    1.企业门户网站(1)根据企业要求调整网站的排版;(2)根据第三方安全测评单位的检测报告对网站系统本身的漏洞和BUG进行修正;(3)在省、市政府测评范围内对网站系统功能进行变更和完......
  • 浅谈群论
    群一些基础子群若\(H\)是\(G\)的子集且\(<H,op>\)为群,则\(<H,op>\)为\(<G,op>\)的子群则\(H\)既满足封闭性且求逆封闭,\(\foralla,b\inH,ab\inH,a^{-1}\inH\)等......
  • 浅谈SQL Server 对于内存的管理
    简介   理解SQLServer对于内存的管理是对于SQLServer问题处理和性能调优的基本,本篇文章讲述SQLServer对于内存管理的内存原理。 二级存储(secondarystorage)......
  • 《权值线段树详解》——代码仓库
    P3369【模板】普通平衡树#include<bits/stdc++.h>#defineintlonglong#definels(t[i].l)#definers(t[i].r)#definemid((l+r)>>1)usingnamespacestd;co......
  • 权值线段树详解
    前言首先明确,权值线段树是线段树的一个子集。它的思想与线段树一模一样,只是实现的功能更加神奇。前置知识:线段树树状数组本文将介绍权值线段树、可持久化权值线段树......
  • 【YBT2023寒假Day3 C】樱桃莓莓(凸包)(线段树)
    樱桃莓莓题目链接:YBT2023寒假Day3C题目大意给你一棵有根数,点有a,b两种权值。然后一个点的分数是它以及它所有祖先的a权值和的绝对值乘上b权值和的绝对值。然后......
  • 算法:线段树&&Luogu p3372题解
    前言不愧是线段树,竟然卡我这么久,还是那句话:十年OI一场空,不开longlong见祖宗#1什么是线段树?线段树长什么样?通俗一点,线段树就是线段,树。实际上,线段树是一棵完全......
  • 【Flink】浅谈Flink架构和调度
    【Flink】浅谈Flink架构和调度大家好,我们的gzh是朝阳三只大明白,满满全是干货,分享近期的学习知识以及个人总结(包括读研和IT),跪求一波关注,希望和大家一起努力、进步!!Flink架构Fl......