数据结构|序列问题与树上问题小结

好累啊这几天沉迷数据结构
高数被我鸽几天了,单词又背了遍abandon...
总结一下这几天沉迷的成果,这些东西虽然好玩,但是留给我的时间不多了,2020都过了好多天了
感觉要是不看爱情公寓,可以再多刷个两道题。。

序列问题

珂朵莉树

之前写过了,https://www.cnblogs.com/fisherss/p/12182869.html

线段树常用模板:单点更新,区间更新,维护最值

https://www.cnblogs.com/fisherss/p/10920642.html

P3373 线段树维护区间乘、区间加

题解:https://www.luogu.com.cn/problemnew/solution/P3373
多个标记分清楚,标记的优先级顺序

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
ll mod;
ll a[maxn];

struct node{
    ll v,mul,add;
}tree[maxn*4+10];

void pushup(int o){
    tree[o].v = (tree[o<<1].v + tree[o<<1|1].v)%mod;
}

void pushdown(int o,int l,int r){
    //这里要干什么
    int mid = (l+r)>>1;
    //更新子节点的sum: 子节点sum = v*父节点mul + 父节点标记的add值 * 子节点区间长度 
    tree[o<<1].v = (tree[o<<1].v * tree[o].mul + tree[o].add * (mid-l+1))%mod;
    tree[o<<1|1].v = (tree[o<<1|1].v * tree[o].mul + tree[o].add * (r-(mid+1)+1))%mod;
    //更新子节点的mul
    tree[o<<1].mul = (tree[o<<1].mul * tree[o].mul)%mod;
    tree[o<<1|1].mul = (tree[o<<1|1].mul * tree[o].mul)%mod;
    //更新子节点的add
    tree[o<<1].add = (tree[o<<1].add * tree[o].mul + tree[o].add)%mod; //先成后加
    tree[o<<1|1].add = (tree[o<<1|1].add * tree[o].mul + tree[o].add)%mod;
    //清除父节点的标记 
    tree[o].mul = 1;
    tree[o].add = 0; 
}

void build(int o,int l,int r){
    //初始化所有结点的lazy标记 
    tree[o].mul = 1;
    tree[o].add = 0;
    if(l == r){ //叶节点 
        tree[o].v = a[l]%mod;
        return;
    }
    int mid = (l+r)>>1;
    build(o<<1,l,mid);
    build(o<<1|1,mid+1,r);
    pushup(o);
}

//区间乘 
void update1(int o,int l,int r,int ql,int qr,ll k){
    if(ql<=l && r<=qr){
        tree[o].v = (tree[o].v * k)%mod;
        tree[o].mul = (tree[o].mul * k)%mod;
        tree[o].add = (tree[o].add * k)%mod;
        return; 
    }
    pushdown(o,l,r);
    int mid = (l+r)>>1;
    if(ql<=mid) update1(o<<1,l,mid,ql,qr,k);
    if(qr>mid) update1(o<<1|1,mid+1,r,ql,qr,k);
    pushup(o);
}

//区间加 
void update2(int o,int l,int r,int ql,int qr,ll k){
    if(ql<=l && r<=qr){
        tree[o].v = (tree[o].v + (r-l+1)*k)%mod;
        tree[o].add = (tree[o].add + k)%mod;
        return;
    }
    pushdown(o,l,r);
    int mid = (l+r)>>1;
    if(ql<=mid) update2(o<<1,l,mid,ql,qr,k);
    if(qr>mid) update2(o<<1|1,mid+1,r,ql,qr,k);
    pushup(o);
}

//区间查询 
ll querysum(int o,int l,int r,int ql,int qr){
    if(ql<=l && r<=qr){
        return tree[o].v%mod; 
    }
    pushdown(o,l,r);
    int mid = (l+r)>>1;
    ll ans = 0;
    if(ql <= mid) ans = (ans + querysum(o<<1,l,mid,ql,qr))%mod;
    if(qr >= mid+1) ans = (ans + querysum(o<<1|1,mid+1,r,ql,qr))%mod;
    return ans%mod;
}

int main(){
    int n, m;
    scanf("%d%d%d", &n, &m, &mod);
    for(int i=1; i<=n; i++){
        scanf("%lld", &a[i]);
    }
    build(1, 1, n);
    while(m--){
        int opt;
        scanf("%d", &opt);
        int x, y;
        long long k;
        if(opt==1){ //区间乘 
            scanf("%d%d%lld", &x, &y, &k);
            update1(1, 1, n, x, y, k);
        }else if(opt==2){ //区间加 
            scanf("%d%d%lld", &x, &y, &k);
            update2(1, 1, n, x, y, k);
        }else{ //查询区间和 
            scanf("%d%d", &x, &y);
            printf("%lld\n", querysum(1, 1, n, x, y));
        }
    }
    return 0;
}

P4145 线段树区间开根号

考虑到开根号是个很快的操作,一个区间开着开着就变成了0或者1
维护下线段树的某个区间是不是全0或者全1,是的话不管,不是的话暴力开根号即可。


//区间修改 转变成了 →单点暴力开根号↓
//每进入一个最小区间(只有一个值)开根号,更新标记,向上up更新
void change(int o,int l,int r,int ql,int qr) {
    if(setv[o]) return;
    if(l==r) { //每进入一个最小区间(l==r时区间SSEd@只有一个值)
        sum[o]=(ll)sqrt(sumv[o]);//开根号
        if(sumv[o]==1||sumv[o]==0) setv[o]=1; //新标记
        return;
    }
    int mid = (l+r)>>1;
    if(ql<=mid) change(lson,mid,ql,qr);
    if(qr>mid) change(rson,mid+1,r,ql,qr);
    pushup(o); //向上up更新
}

P4513 线段树维护最大子段和

这道题学习参考点:主要是"合并"
对于每个区间,维护一个左边的最大前缀,右边的最大后缀,以及区间内部的总答案
每次合并的时候,即答案选取左子区间的max,右子区间的max,或者左子区间的最大后缀,右子区间的最大前缀即可
题解1:https://www.luogu.com.cn/blog/41302/solution-p4513
题解2:https://www.luogu.com.cn/blog/user52559/solution-p4513
借个图

P2572 珂朵莉树|线段树维护复杂信息

这道题还可以用珂朵莉树做,暴力优雅。
线段树题解1:https://www.luogu.com.cn/blog/QVQ/solution-p2572
线段树的合并思想类似小白逛公园,分左、右、跨越区间三种清空;
优先级的问题类似于区间乘那题乘法优先,这里就是赋值优先
借个图

P1712 双指针 + 线段树(待补)

题目:https://www.luogu.com.cn/problem/P1712
题解:https://www.luogu.com.cn/blog/user5680/solution-p1712

然后接触到,线段树动态开点

思想学习:https://blog.csdn.net/lvmaooi/article/details/79729437
理解了,动态开点就是 动态给结点分配编号,不用先build建树了
动态开点,每个结点最多开logn个,所以空间复杂度就是O(nlogn)
*又听说,类似主席树的写法,到时候学到再补

P1908 逆序对,线段树+动态开点做法

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lson l,mid,tree[root].l
#define rson mid+1,r,tree[root].r
#define ls tree[root].l
#define rs tree[root].r
const int maxn = 5e5+10;
const int inf = 1e9+5;

//为什么要动态开点:因为这道题数字个数大1e9,不动态开点就先要离散化再建线段树 
//静态开点的左孩子是root*2,右孩子是root*2+1,现在按编号分配 

struct node{
    int l,r,sum;
}tree[maxn*32];

int cnt = 1;

//合并更新父节点 
void pushup(int root){
    tree[root].sum=tree[tree[root].l].sum+tree[tree[root].r].sum;
} 

void update(int &root,int l,int r,int pos){
    if(!root) root = ++cnt; //动态分配"结点编号"  
    if(l == r){
        tree[root].sum++;
        return;
    }
    int mid = (l + r)>>1;
    if(pos <= mid) update(tree[root].l,l,mid,pos); //要更新的pos点在前半段区间 
    else update(tree[root].r,mid+1,r,pos); //要更新的pos点在后半段区间
    pushup(root);
}

ll query(int root,int l,int r,int ql,int qr){
    ll ans = 0;
    if(ql <= l && r <= qr){ //如果完全包含(l,r)区间 
        return tree[root].sum;
    }
    int mid = (l + r)>>1;
    if(ql <= mid) ans += query(tree[root].l,l,mid,ql,qr); //要查询的区间包含了左边一侧 查左边 
    if(qr > mid) ans += query(tree[root].r,mid+1,r,ql,qr); //要查询的区间包含了右边一侧 查右边 
    return ans;
} 

int main(){
    int n;
    cin>>n;
    ll ans = 0;
    int root = 1;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        ans += query(1,1,inf,x+1,inf); //查询当前输入下已经比x大的数右多少个,即(x+1,inf)范围内已插入数的个数 
        update(root,1,inf,x); //x位置上个数+1 
    } 
    cout<<ans<<endl;
    return 0;
}

/*
参考链接:https://blog.csdn.net/qq_43906000/article/details/102155429
类似题目:https://blog.csdn.net/u012972031/article/details/88751811 
*/

P1908 线段树 + 离散化做法

#include<bits/stdc++.h>
#define fi first
#define se second
#define INF 0x3f3f3f3f
#define ll long long
#define ld long double
#define mem(ar,num) memset(ar,num,sizeof(ar))
#define me(ar) memset(ar,0,sizeof(ar))
#define lowbit(x) (x&(-x))
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define lcm(a,b) ((a)*(b)/(__gcd((a),(b))))
#define maxn 500010
#define mod 1000000007
using namespace std;

ll n, tree[maxn << 2], arr[maxn], temp[maxn], ans;

void pushup(int root) {
    tree[root] = tree[root << 1] + tree[root << 1 | 1];
}

//初始化各个值都是0 
void build(int root,int l,int r){
    if(l == r){
        tree[root] = 0;
        return; 
    }
    int mid = (l+r)>>1;
    build(root << 1,l,mid);
    build(root <<1 | 1,mid+1,r);
    pushup(root);
}

void update(int root,int l, int r,int pos) {
    if(l == r) {
        tree[root]++;
        return;
    }
    int mid = (l + r) >> 1;
    if(mid >= pos) update(root << 1, l, mid, pos);
    else update(root << 1 | 1,mid + 1, r, pos);
    pushup(root);
}

ll query( int root,int ql, int qr, int l, int r) {
    if(ql <= l && r <= qr) {
        return tree[root];
    }
    int mid = (r + l) >> 1;
    ll ans = 0;
    if(ql <= mid) ans += query(root << 1, ql, qr, l, mid);
    if(qr > mid) ans += query(root << 1 | 1,ql, qr, mid + 1, r);
    return ans;
}

int main() {
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> arr[i], temp[i] = arr[i];
    //离散化开始
    sort(temp + 1, temp + n + 1);
    int num = unique(temp + 1, temp + n + 1) - temp - 1;
    for(int i = 1; i <= n; i++)
        arr[i] = lower_bound(temp + 1, temp + num + 1, arr[i]) - temp; //lower_bound - tempp 就是编号了 
    //离散化结束↑ 
    build(1,1,50001); //这一步可以省略 无需先建树 
    for(int i = 1; i <= n; i++) {
        update(1, 1, n, arr[i]);
        ans += query(1, arr[i] + 1, n, 1, n);
    }
    cout << ans;
    return 0;
}

/*
参考链接:https://blog.csdn.net/endeavor_g/article/details/88654684 
*/

HPU校赛 线段树动态开点维护前缀和后缀和

敲了一遍竟然过了,有点开心啊!
https://www.cnblogs.com/fisherss/p/12104701.html

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
int n;
ll a[maxn];
int k[maxn];


ll min(ll a,ll b){
    if(a < b) return a;
    else return b;
}

struct tree{
    ll sum[maxn];
    
    void pushup(int root){
        sum[root] = min(sum[root<<1],sum[root<<1|1]);
    }
    
    void build(int root,int l,int r){
        if(l == r){
            sum[root] = 0;
            return;
        }
        int mid = (l+r)>>1;
        build(root<<1,l,mid);
        build(root<<1|1,mid+1,r);
        pushup(root);
    }
    
    void update(int root,int l,int r,int pos,int v){
        if(l == r){
            sum[root] = v;
            return;
        }
        int mid = (l + r) >> 1;
        if(pos <= mid) update(root<<1,l,mid,pos,v);
        else update(root<<1|1,mid+1,r,pos,v);
        pushup(root);
    }
    
    ll query(int root,int l,int r,int ql,int qr){
        if(ql == 0 && qr == 0) return 0; //必须处理 查询长度为0时的边界 
        if(ql <= l && r <= qr){
            return sum[root];
        }
        int mid = (l + r) >> 1;
        ll ans = 0x3f3f3f3f;
        if(ql <= mid) ans = min(ans,query(root<<1,l,mid,ql,qr));
        if(qr > mid) ans = min(ans,query(root<<1|1,mid+1,r,ql,qr));
        return ans; 
    }
    
}tp,tn;

int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>k[i];
    for(int i=1;i<=n;i++) cin>>a[i];
    ll sum = 0;
    tp.build(1,1,n);
    for(int i=1;i<=n;i++){
        sum += a[i];//前缀
        tp.update(1,1,n,i,sum); 
    }
    sum = 0;
    tn.build(1,1,n);
    for(int i=n;i>=1;i--){
        sum += a[i];//后缀 
        tn.update(1,1,n,i,sum);
    }
    ll ans = 0;
    //求s1 + ... + sn 
    for(int i=1;i<=n;i++){
        ans += sum;
        ans -= tp.query(1,1,n,max(i-k[i]-1,0),max(i-1,0)); //除了i以外的前缀 
        ans -= tn.query(1,1,n,min(i+1,n+1),min(i+k[i]+1,n+1)); //除了i以外的后缀
    } 
    cout<<ans;
    return 0; 
} 

/*
5
1 2 3 4 4
-5 1 2 3 -4


16
*/

扫描线 与 线段树问题

讲解最好的博客1:https://blog.csdn.net/xianpingping/article/details/83032798
HDU1542参考题解2:https://www.cnblogs.com/liwenchi/p/7259171.html

借用一下博客1的代码,加了部分注释

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 99999999
using namespace std;

/*
个人感觉:
    相当于线段树在线开点 插入新点 维护sum边长 
*/ 
 
const int MAX=200+10;
int mark[MAX<<2];//记录某个区间的下底边个数
double sum[MAX<<2];//记录某个区间的下底边总长度
double value[MAX];//对x进行离散化,否则x为浮点数且很大无法进行线段树 
 
//以横坐标作为线段(区间),对横坐标线段进行扫描
//扫描的作用是每次更新下底边总长度和下底边个数,增加新面积 
struct seg{//线段
    double l,r,h;
    int d;
    seg(){}
    seg(double x1,double x2,double H,int c):l(x1),r(x2),h(H),d(c){}
    bool operator<(const seg &a)const{
        return h<a.h;
    }
}s[MAX];

void pushup(int o,int left,int right){ 
    if(mark[o]) sum[o]=value[right+1]-value[left];//mark[o]!=0表示包含了整个子区间,该子区间整个线段长度可以作为底边 
    else if(left == right)sum[o]=0;//叶子结点则底边长度为0(区间内线段长度为0) 
    else sum[o]=sum[o<<1]+sum[o<<1|1];
}
 
void update(int L,int R,int d,int o,int left,int right){
    if(L<=left && right<=R){//该区间是当前扫描线段的一部分,则该区间下底边总长以及上下底边个数差更新 
        mark[o]+=d;//更新底边相差差个数 
        pushup(o,left,right);//更新底边长 
        return;
    }
    int mid=left+right>>1;
    if(L<=mid)update(L,R,d,o<<1,left,mid);
    if(R>mid)update(L,R,d,o<<1|1,mid+1,right);
    pushup(o,left,right);
}

//二分查找 
int search(double key,double* x,int n){
    int left=0,right=n-1;
    while(left<=right){
        int mid=left+right>>1;
        if(x[mid] == key)return mid;
        if(x[mid]>key)right=mid-1;
        else left=mid+1;
    }
    return -1;
}
 
int main(){
    int n,num=0;
    double x1,x2,y1,y2;
    while(cin>>n,n){
        int k=0;
        for(int i=0;i<n;++i){
            cin>>x1>>y1>>x2>>y2;
            value[k]=x1; //记录树结点->但是记录的离散化前的 
            s[k++]=seg(x1,x2,y1,1); //记录扫描线 下边位 
            value[k]=x2;
            s[k++]=seg(x1,x2,y2,-1); //记录扫描线 上边位 
        }
        sort(value,value+k);
        sort(s,s+k);
        int m=1;
        for(int i=1;i<k;++i)//去重复端点 
            if(value[i] != value[i-1])value[m++]=value[i];
        double ans=0;
        //memset(mark,0,sizeof mark);
        //memset(sum,0,sizeof sum);如果下面是i<k-1则要初始化,因为如果对第k-1条线段扫描时会使得mark,sum为0才不用初始化的 
        for(int i=0;i<k;++i){//扫描线段
            int L=search(s[i].l,value,m);
            int R=search(s[i].r,value,m)-1;
            update(L,R,s[i].d,1,0,m-1);//扫描线段时更新底边长度和底边相差个数
//          cout<<ans<<" "<<sum[1]*(s[i+1].h-s[i].h)<<endl;
            ans+=sum[1]*(s[i+1].h-s[i].h);//新增加面积
        }
        printf("Test case #%d\nTotal explored area: %.2lf\n\n",++num,ans);
    }
    return 0;
}
/*
这里注意下
扫描线段时r-1:int R=search(s[i].l,value,m)-1;
计算底边长时r+1:if(mark[n])sum[n]=value[right+1]-value[left];
解释:假设现在有一个线段左端点是l=0,右端点是r=m-1
则我们去更新的时候,会算到sum[1]=value[mid]-value[left]+value[right]-value[mid+1]
这样的到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,由于我们这利用了
离散化且区间表示线段,所以mid~mid+1之间是有长度的,比如value[3]=1.2,value[4]=5.6,mid=3
所以这里用r-1,r+1就很好理解了 
*/ 

树上问题

树链剖分

学习地址1:https://www.bilibili.com/video/av4482146
学习地址2:https://www.bilibili.com/video/av24798851
详细博客1:https://www.cnblogs.com/chinhhh/p/7965433.html
用法总结1:https://blog.csdn.net/qq_41730604/article/details/101453877
树剖入门到入土的题目总结:https://www.cnblogs.com/Isaunoya/p/11619823.html

树链剖分求LCA(模板P3379)

我我我终于写出了自己的LCA!
还是喜欢vector啊

#include<bits/stdc++.h>
using namespace std;

const int maxn = 5e5+100;
vector<int> g[maxn];
/*  父亲,    深度,       子节点数, 重儿子,   dfs序,   dfs映射,链头,    链尾    */
int fa[maxn],depth[maxn],sz[maxn],son[maxn],id[maxn],rk[maxn],top[maxn],bot[maxn];
int cnt = 0;

void dfs(int x,int deep){
    depth[x] = deep;
    sz[x] = 1;
    for(int li = 0;li<g[x].size();li++){
        int i = g[x][li];
        if(i == fa[x]) continue;
        fa[i] = x;
        dfs(i,deep+1);
        sz[x] += sz[i];
        if(sz[i] > sz[son[x]]) son[x] = i;
    }
}

void dfs2(int x,int tp){
    top[x] = tp;
    id[x] = ++cnt;
    rk[cnt] = x;
    if(son[x]) dfs2(son[x],tp),bot[x] = bot[son[x]];
    else bot[x] = x;
    for(int li=0;li<g[x].size();li++){
        int i = g[x][li];
        if(i != fa[x] && i != son[x])
            dfs2(i,i);
    }
}

int lca(int u,int v){
    while(top[u] != top[v]){
        if(depth[top[u]] < depth[top[v]]) swap(u,v);
        u = fa[top[u]];
    }
    if(depth[u] < depth[v]) return u;
    return v;
}

int main(){
    ios::sync_with_stdio(false);
    int n,m,root;
    cin>>n>>m>>root;
    for(int i=1;i<=n-1;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(root,1);
    dfs2(root,root);
    while(m--){
        int u,v;
        cin>>u>>v;
        cout<<lca(u,v)<<endl;
    }
    return 0;
}

还有很多...吃不消了

待补

猜你喜欢

转载自www.cnblogs.com/fisherss/p/12207332.html