线段树分治总结

目录

  • 类型一

    • 例题1:八纵八横

      • 代码:
    • 例题2:时空旅行

首先,要求可以离线
线段树分治有两种。

类型一

操作基于区间,单点询问。

有时,进行的一种操作可以快速完成,但是,要实现这种操作的逆操作较难。
因为,通常情况下,需要实现的逆操作都是很久以前执行的。
但是,如果只撤销上次操作,就会简单得多。
比如,维护一些连通性,或直径,线性基等问题。
这类问题加边很好做,但删边很难实现。
我们可以扫一遍操作,得到每个操作的有效区间。
然后,将每个添加操作的有效区间按在线段树上,然后遍历这颗线段树同时处理标记即可。
从某种角度,可以理解为标记永久化。
这样,就将撤销任意一次变为只撤销上一次。(还是要撤销)
要求:用于维护的数据结构支持撤销上一操作,复杂度不能均摊(因为要撤销)
时间复杂度:比正常多一个log。

例题1:八纵八横

题目链接:[HAOI2017]八纵八横

线段树分治&线性基 模板题。

给一棵树,支持加边,删边,修改边权,并询问最大异或和的环。
类似xor和路径,询问结果就是所有环的最大异或和,使用线性基。
修改可以看做删除+插入。由于线性基不支持删除,所以使用线段树分治。
可以用并查集维护树。

代码:

#include <stdio.h>
#include <bitset>
#include <string.h>
#include <vector>
using namespace std;
int fr[503],ne[1003],v[1003],w[1003],bs = 0,len,ff[503];
bool bk[1003],ca[1003];
bitset < 1005 > bi[2003],jl[503],ji[1003],ans[1003];
void addb(int a, int b, int c) {
    v[bs] = b;
    w[bs] = c;
    ne[bs] = fr[a];
    fr[a] = bs++;
}
void dfs1(int u, int f) {
    for (int i = fr[u]; i != -1; i = ne[i]) {
        if (v[i] == f) continue;
        jl[v[i]] = jl[u] ^ bi[w[i]];
        dfs1(v[i], u);
    }
}
int getv(int x) {
    if (x == ff[x]) return x;
    ff[x] = getv(ff[x]);
    return ff[x];
}
bool merge(int x, int y) {
    x = getv(x);
    y = getv(y);
    if (x == y) return false;
    ff[x] = y;
    return true;
}
void fuz(bitset < 1005 > &x, char zf[1005]) {
    x = 0;
    int n = strlen(zf);
    if (n > len) len = n;
    for (int i = 0; i < n; i++) {
        if (zf[i] == '1') x[n - 1 - i] = 1;
    }
}
void getans(bitset < 1005 > &x) {
    x = 0;
    for (int i = len - 1; i >= 0; i--) {
        if (x[i] == 0 && bk[i]) x ^= ji[i];
    }
}
int st[1005],tp = 0;
void insert(bitset < 1005 > x) {
    for (int i = len - 1; i >= 0; i--) {
        if (x[i]) {
            if (!bk[i]) {
                bk[i] = true;
                ji[i] = x;
                st[tp++] = i;
                break;
            } else x ^= ji[i];
        }
    }
}
struct SJd {
    int x,y,z;
    SJd() {}
    SJd(int X, int Y, int Z) {
        x = X;y = Y;z = Z;
    }
};
vector < SJd > ve[8005];
void xiugai(int i, int l, int r, int L, int R, SJd x) {
    if (R <= l || r <= L) return;
    if (L <= l && r <= R) {
        ve[i].push_back(x);
        return;
    }
    int m = (l + r) >> 1;
    xiugai(i << 1, l, m, L, R, x);
    xiugai((i << 1) | 1, m, r, L, R, x);
}
int wz[2003];
void dfs3(int i, int l, int r) {
    int la = tp;
    for (int j = 0; j < ve[i].size(); j++) insert(bi[ve[i][j].z] ^ jl[ve[i][j].x] ^ jl[ve[i][j].y]);
    if (l + 1 == r) {
        if (wz[l] != -1) getans(ans[wz[l]]);
    } else {
        int m = (l + r) >> 1;
        dfs3(i << 1, l, m);
        dfs3((i << 1) | 1, m, r);
    }
    for (int i = la; i < tp; i++) bk[st[i]] = false;
    tp = la;
}
char zf[1003],ch[20];
int tx[1003],ty[1003],la[1003],tm[1003];
int ll[2003],rr[2003],X[2003],Y[2003],Z[2003];
SJd xg[2003];
int main() {
    int n,m,q;
    scanf("%d%d%d", &n, &m, &q);
    for (int i = 1; i <= n; i++) {
        fr[i] = -1;ff[i] = i;
    }
    for (int i = 0; i < m; i++) {
        int x,y;
        scanf("%d%d%s", &x, &y, zf);
        fuz(bi[i], zf);
        if (merge(x, y)) {
            addb(x, y, i);addb(y, x, i);
        } else tx[i] = x,
        ty[i] = y;
    }
    dfs1(1, 0);
    int ss = 0,ks = 0,xs = 0;
    for (int i = 1; i <= q; i++) {
        scanf("%s", ch);
        if (ch[0] == 'A') {
            int x,y;
            scanf("%d%d%s", &x, &y, zf);
            ks += 1;ss += 1;
            wz[ss] = i;
            fuz(bi[m + i], zf);
            la[ks] = ss;tm[ks] = i;
            X[ks] = x;Y[ks] = y;
        } else if (ch[0] == 'C' && ch[1] == 'a') {
            int k;
            scanf("%d", &k);
            ss += 1;wz[ss] = i;
            xg[xs] = SJd(X[k], Y[k], m + tm[k]);
            ll[xs] = la[k];rr[xs] = ss;
            xs += 1;
            ca[k] = true;
        } else {
            int k;
            scanf("%d%s", &k, zf);
            fuz(bi[m + i], zf);
            ss += 1;wz[ss] = -1;
            xg[xs] = SJd(X[k], Y[k], m + tm[k]);
            ll[xs] = la[k];rr[xs] = ss;
            xs += 1;wz[ss] = i;
            tm[k] = i;la[k] = ss;
        }
    }
    for (int i = 0; i < m; i++) {
        if (tx[i]) insert(bi[i] ^ jl[tx[i]] ^ jl[ty[i]]);
    }
    tp = 0;
    for (int k = 1; k <= ks; k++) {
        if (ca[k]) continue;
        xg[xs] = SJd(X[k], Y[k], m + tm[k]);
        ll[xs] = la[k];
        rr[xs] = ss + 1;
        xs += 1;
    }
    for (int i = 0; i < xs; i++) xiugai(1, 0, ss + 1, ll[i], rr[i], xg[i]);
    dfs3(1, 0, ss + 1);
    for (int i = 0; i <= q; i++) {
        bool zz = false;
        for (int j = len - 1; j >= 0; j--) {
            if (ans[i][j] == 1) {
                printf("1");
                zz = true;
            } else if (zz) printf("0");
        }
        printf("\n");
    }
    return 0;
}

例题2:时空旅行

题目链接:[CTSC2016]时空旅行

题意:
在一棵树上,每个节点代表一个集合,一些元素存在这个集合之中,
每个节点上的集合,是由父亲的先复制下来,然后添加或删除1个元素,成为一个新的集合。
每个元素有\((x,y,z,c)\)四个值,\((y,z)\)没用,就是两个\((x,c)\)。
每次给出树上一个点,以及一个X,要求出这个节点所有元素的\(min((X?x_i)^2+C_i)\)。
要求复杂度\(O(nlogn)\)。

首先,看到\(min((X?x_i)^2+C_i)\),很自然想到斜率优化。
\(y_i=x_i^2+C_i,y_i=x_i,k=2X,b=y-kx,ans=b+X^2\)。
那么,相当于,每个节点的凸包,是由父亲的先复制下来,然后添加或删除1个点,成为一个新的凸包。

可以发现,这是一个版本树,遍历一下,就变成序列上的了。而且也是单点询问。

原文地址:https://www.cnblogs.com/lnzwz/p/11614311.html

时间: 2024-10-12 04:29:58

线段树分治总结的相关文章

3237: [Ahoi2013]连通图 线段树分治

题解: 线段树分治裸题 apio t1是这个所以就学习了一下 #include <bits/stdc++.h> using namespace std; const int N=2e5+10; struct re{ int x,y; }a[N*2]; int b[N+20][5],cnt,now,n,m,k; int ls[N*15],rs[N*15],data[N*15],last[N+20]; int ph[N*4],pt[N*4],count2[N+20],f[N]; bool ft[N

线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)

闲话 stO猫锟学长,满脑子神仙DS 线段树分治思想 我们在做CDQ的时候,将询问和操作通通视为元素,在归并过程中统计左边的操作对右边的询问的贡献. 而在线段树分治中,询问被固定了.按时间轴确定好询问的序列以后,我们还需要所有的操作都会影响一个时间区间.而这个区间,毫无疑问正好对应着询问的一段区间. 于是,我们可以将每一个操作丢到若干询问里做区间修改了,而线段树可以高效地维护.我们开一个叶子节点下标为询问排列的线段树,作为分治过程的底层结构. 具体的实现,仍然要看题目. 例题1 BZOJ4025

算法学习——动态图连通性(线段树分治+按秩合并并查集)

在考场上遇到了这个的板子题,,,所以来学习了一下线段树分治 + 带撤销的并查集. 题目大意是这样的:有m个时刻,每个时刻有一个加边or撤销一条边的操作,保证操作合法,没有重边自环,每次操作后输出当前图下所有联通块大小的乘积. 首先观察到如果没有撤销操作,那么直接用并查集就可以维护,每次合并的时候乘上要合并的两个并查集大小的逆元,然后乘上合并之后的大小即可. 那么来考虑撤销,观察到如果并查集不带路径压缩,应该是可以处理撤销操作的. 但我们并不能直接做,因为并查集的撤销必须按顺序来,就相当于每次合并

【线段树分治 线性基】luoguP3733 [HAOI2017]八纵八横

不知道为什么bzoj没有HAOI2017 题目描述 Anihc国有n个城市,这n个城市从1~n编号,1号城市为首都.城市间初始时有m条高速公路,每条高速公路都有一个非负整数的经济影响因子,每条高速公路的两端都是城市(可能两端是同一个城市),保证任意两个城市都可以通过高速公路互达. 国正在筹划“八纵八横”的高铁建设计划,计划要修建一些高速铁路,每条高速铁路两端也都是城市(可能两端是同一个城市),也都有一个非负整数的经济影响因子.国家还计划在“八纵八横”计划建成之后,将“一带一路”扩展为“一带_路一

bzoj 4137 [FJOI2015]火星商店问题——线段树分治+可持久化01trie树

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4137 关于可持久化01trie树:https://www.cnblogs.com/LadyLex/p/7281110.html 看了看它的两道例题,就没写. 特殊商品可以直接用可持久化trie做. 其他部分用线段树分治.修改是单点的,询问是区间,原来想的是把询问区间定位后有 mlogn 个,在线段树的每个叶子上贡献一番:结果TLE了,因为若是在叶子处贡献,一个询问就要做 r-l+1 次.

loj#2312. 「HAOI2017」八纵八横(线性基 线段树分治)

题意 题目链接 Sol 线性基+线段树分治板子题.. 调起来有点自闭.. #include<bits/stdc++.h> #define fi first #define se second #define pb push_back #define bit bitset<B + 1> using namespace std; const int MAXN = 501, B = 1001, SS = 4001; inline int read() { char c = getchar

HAOI2017 八纵八横——线段树分治+线性基

题目大意 给定一个图,每次加一些边,或者删掉一些后来加上去的边,定义一个环的价值为环上所有的边的异或和,重复走的边重复算.每次询问这个时刻图中的所有经过1号点的环的最大价值. 思路 首先考虑对于一个静态的图如何求解图中所有经过1号点的环的最大价值,发现这个经过1号点就是唬人的,图中任意一个环都可以经过1号点再走回来. 于是题目变成了求解图中环的最大价值,可以将图中所有的简单环给拎出来放到线性基里面求最大价值,不难发现这是对的. 然后题目转化为了如何求图中所有的简单环,一般我们可以直接对图dfs找

bzoj4311向量(线段树分治+斜率优化)

第二道线段树分治. 首先设当前向量是(x,y),剩余有两个不同的向量(u1,v1)(u2,v2),假设u1>u2,则移项可得,若(u1,v1)优于(u2,v2),则-x/y>(v1-v2)/(u1-u2),然后维护上凸壳后进行三分即可,复杂度O(nlog2n),如果将询问排序扫一遍,可以优化到O(nlogn),当然我没写. #include<bits/stdc++.h> #define lson l,mid,rt<<1 #define rson mid+1,r,rt&l

线段树分治

2014徐寅展论文<线段树在一类分治问题上的应用>读后感. 线段树分治 线段树分治其实就是有撤销操作的时间分治. 题目让你维护一些信息,每次可以询问,可以执行一种操作,也可以将之前的某个这种操作撤回. 操作容易维护,但撤回操作不容易维护. 需要将操作,询问都离线下来.将时间轴画出来,那么每个操作只在时间轴上的一个区间内生效. 用线段树给这个区间打上这个操作的标记,维护信息. TJOI2018 数学计算 小豆现在有一个数x,初始值为1. 小豆有Q次操作,操作有两种类型: m: x = x * m