• 线段树合并


    线段树合并说全来就是动态开点权值线段树合并。所以你需要掌握权值线段树的基本知识以及知道什么是动态开点(雾

    线段树合并的主要方式如下:

    对于两棵线段树都有的节点,新的线段树的该节点值为两者和。

    对于某一棵线段树有的节点,新的线段树保存该节点的值。

    然后对左右子树递归处理。

    不能理解?那就看一下代码。

    int merge (int l, int r, int u, int v)
    {
        if (!u || !v) return u|v;
        if (l==r) {return val[++tot]=val[u]+val[v], tot;}
        int mid=l+r>>1, node=++tot;
        //若干操作
        ls[node]=merge(l, mid, ls[u], ls[v]);
        rs[node]=merge(mid+1, r, rs[u], rs[v]);
        val[node]=val[ls[node]]+val[rs[node]];
        return node;
    }
    

    看起来很暴力?那么复杂度是多少?

    合并的复杂度显然与两棵线段树重合的叶子结点个数有关,实际上若叶子个数为(m),那么一次合并的复杂度就是(mlogn)了。

    我要合并(n)次怎么办?复杂度(O(nmlogn))

    其实是(O(nlogn)),可是我不会证。引用洛谷日报的证明:

    来证明一下:
    假设我们会加入(k)个点,由上面的结论,我们可以推出最多要新增(klogk)个点。
    而正如我们所知,每次合并两棵线段树同位置的点,就会少掉一个点,复杂度为(O(1)) ,总共(O(klogk))个点,全部合并的复杂度就是(O(klogk))

    例题:POI2011 ROT-Tree Rotations

    给出一棵(n)个叶子的二叉树,仅叶子有点权,求通过变换任意几个点的左右子树使得前序遍历叶子的逆序对最小值。

    (dfs)整棵树,对于一个节点,交换子树使逆序对更少则交换。如何求逆序对?线段树合并即可。

    不过该题需要注意写法,不能每次合并到新开的节点,而应直接将线段树(a)合并到线段树(b)上,不然会(MLE)

    #include<cstdio>
    #define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
    #define per(i, a, b) for (register int i=(a); i>=(b); --i)
    using namespace std;
    const int N=6000005;
    long long min(long long a, long long b){return a<b?a:b;}
    int ls[N], rs[N], val[N], n, tot;
    long long ans, ans1, ans2;
    
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
        for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
        return x*f;
    }
    
    int New(int l, int r, int x)
    {
        val[++tot]=1;
        if (l==r) return tot;
        int mid=l+r>>1, node=tot;
        if (x<=mid) ls[node]=New(l, mid, x);
            else rs[node]=New(mid+1, r, x);
        return node;
    }
    
    int merge(int l, int r, int u, int v)
    {
        if (!u || !v) return u+v;
        if (l==r) {val[u]=val[u]+val[v]; return u;}
        int mid=(l+r)>>1, node=u;
        ans1+=1ll*val[rs[u]]*val[ls[v]]; 
        ans2+=1ll*val[ls[u]]*val[rs[v]];
        ls[node]=merge(l, mid, ls[u], ls[v]);
        rs[node]=merge(mid+1, r, rs[u], rs[v]);
        val[node]=val[ls[node]]+val[rs[node]];
        return node;
    }
    
    int dfs()
    {
        int v=read();
        if (v) return New(1, n, v);
        int node=merge(1, n, dfs(), dfs());
        ans+=min(ans1, ans2); ans1=ans2=0;
        return node;
    }
    
    int main()
    {
        n=read(); dfs(); printf("%lld
    ", ans);
        return 0;
    }
    
    

    另一道题目:雨天的尾巴

    题意如下:给定一棵树,每次对一条路径上的每个节点的可重集中加入一个数。求若干次操作后每个节点可重集中出现最多的元素。

    树上路径统计信息的题很容易想到树上差分,如果是(u->v)的路径,在(u,v)(+1),在(LCA(u,v),fa_{LCA(u,v)})(-1)。因为要维护多种元素,所以每个节点开一个权值线段树。

    然后最后从下到上线段树合并即可。

    时间复杂度(O(nlogn))

    本题空间略卡,所以线段树合并写的是覆盖式的而不是新开节点,并且写了树剖(LCA)

    #include<cstdio>
    #include<vector>
    #define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
    #define per(i, a, b) for (register int i=(a); i>=(b); --i)
    using namespace std;
    const int N=100005, M=30000005;
    int dep[N], size[N], fa[N], son[N], top[N];
    int rt[M], ls[M], rs[M], Max[M], Tag[M];
    int x[N], y[N], z[N], ans[N], tot, R, n, m;
    vector<int> G[N];
    
    inline int read()
    {
     	int x=0,f=1;char ch=getchar();
        for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
        for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
        return x*f;
    }
    
    void dfs1(int u, int f)
    {
        dep[u]=dep[f]+1; size[u]=1; fa[u]=f;
        for (int v: G[u]) if (v^f)
        {
            dfs1(v, u); size[u]+=size[v];
            if (size[v]>size[son[u]]) son[u]=v;
        }
    }
    
    void dfs2(int u, int t)
    {
        top[u]=t;
        if (son[u]) dfs2(son[u], t);
        for (int v: G[u]) if (v^son[u] && v^fa[u]) dfs2(v, v);
    }
    
    int LCA(int u, int v)
    {
        while (top[u]^top[v])
        {
            if (dep[top[u]]<dep[top[v]]) swap(u, v);
            u=fa[top[u]];
        }
        return dep[u]<dep[v]?u:v;
    }
    
    void pushup(int rt)
    {
        if (Max[ls[rt]]>=Max[rs[rt]]) 
            Max[rt]=Max[ls[rt]], Tag[rt]=Tag[ls[rt]];
        else Max[rt]=Max[rs[rt]], Tag[rt]=Tag[rs[rt]];
        if (!Max[rt]) Tag[rt]=0;
    }
    
    void Modify(int &rt, int l, int r, int p, int v)
    {
        if (!rt) rt=++tot;
        if (l==r) {Max[rt]+=v; Tag[rt]=l; return;}
        int mid=l+r>>1;
        if (p<=mid) Modify(ls[rt], l, mid, p, v);
            else Modify(rs[rt], mid+1, r, p, v);
        pushup(rt); return;
    }
    
    int merge(int u, int v, int l, int r)
    {
        if (!u || !v) return u|v;
        if (l==r) {Max[u]+=Max[v]; Tag[u]=l; return u;}
        int mid=l+r>>1;
        ls[u]=merge(ls[u], ls[v], l, mid);
        rs[u]=merge(rs[u], rs[v], mid+1, r);
        pushup(u); return u;
    }
    
    void dfs(int u, int fa)
    {
        for (int v: G[u]) if (v^fa)
            dfs(v, u), rt[u]=merge(rt[u], rt[v], 1, R);
        ans[u]=Tag[rt[u]];
    }
    
    int main()
    {
        n=read(); m=read();
        rep(i, 1, n-1)
        {
            int u=read(), v=read();
            G[u].push_back(v); G[v].push_back(u);
        }
        dfs1(1, 0); dfs2(1, 1);
        rep(i, 1, m) x[i]=read(), y[i]=read(), z[i]=read(), R=max(R, z[i]);	
        rep(i, 1, m)
        {
            int lca=LCA(x[i], y[i]);
            Modify(rt[x[i]], 1, R, z[i], 1);
            Modify(rt[y[i]], 1, R, z[i], 1);
            Modify(rt[lca], 1, R, z[i], -1);
            if (fa[lca]) Modify(rt[fa[lca]], 1, R, z[i], -1);
        }
        dfs(1, 0); 
        rep(i, 1, n) printf("%d
    ", ans[i]);
        return 0;
    }
    
    
  • 相关阅读:
    二叉树的遍历详解:前、中、后、层次遍历(Python实现)
    结对编程——需求建模
    使用 python 与 sqlite3 实现简易的学生信息管理系统
    PowerShell下, MySQL备份与还原遇到的坑
    自动生成四则运算(python实现) 更新
    自动生成四则运算题目(python实现)
    软件工程导论的感想
    矩阵的秩与行列式的几何意义
    微信好友分布分析
    第一次结队作业
  • 原文地址:https://www.cnblogs.com/ACMSN/p/10759810.html
Copyright © 2020-2023  润新知