线段树合并

P1456 Monkey King

链接:https://www.luogu.com.cn/problem/P1456

题意:给定 n 只猴子打 m 次架,每次询问两只猴子,选择它们所在集合中,战斗力最大的两只猴子,将它们的战斗力减半。输出这个减半之后的战斗力,然后将这两个集合合并。如果两只猴子已经在同一个集合,那么就输出 -1.

思路:用并查集维护集合关系。然后用 动态开点权值线段树 + 线段树合并来维护操作。

  • 每次先查询 u 、v 是否在同一个集合。不在,则查询当前集合中的最大战斗力的两只猴子,然后删除战斗力,并插入原来战斗力的一半。然后将这两个集合合并。最后查询答案
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,tot=32768;
int n,m;
int root[maxn],st[maxn*40],ls[maxn*40],rs[maxn*40],no;
int query(int rt,int L,int R)
{
    if(!rt) return 0;
    if(L==R) return L;
    int mid=(L+R)>>1;
    if(st[rs[rt]]) return query(rs[rt],mid+1,R);
    else return query(ls[rt],L,mid);
}
void insert(int &rt,int pos,int L,int R)
{
    if(!rt) rt=++no;
    st[rt]++;
    if(L==R) return;
    int mid=(L+R)>>1;
    if(pos<=mid) insert(ls[rt],pos,L,mid);
    else insert(rs[rt],pos,mid+1,R);
}
void del(int &rt,int pos,int L,int R)
{
    if(!rt) return;
    st[rt]--;
    if(L==R) return;
    int mid=(L+R)>>1;
    if(pos<=mid) return del(ls[rt],pos,L,mid);
    else return del(rs[rt],pos,mid+1,R);
}
int merge(int rt1,int rt2,int L,int R)
{
    if(!rt1||!rt2) return rt1?rt1:rt2;
    int rt=rt1;
    st[rt]+=st[rt2];
    int mid=(L+R)>>1;
    ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
    rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
    return rt;
}
int pa[maxn];
int find(int x)
{
    return x==pa[x]?x:pa[x]=find(pa[x]);
}
int main()
{
    while(~scanf("%d",&n))
    {
        memset(root,0,sizeof(root));
        for(int i=0; i<=no; ++i) st[i]=ls[i]=rs[i]=0;
        no=0;
        for(int i=1; i<=n; ++i)
        {
            int x;
            scanf("%d",&x);
            pa[i]=i;
            insert(root[i],x,1,tot);
        }
        scanf("%d",&m);
        int u,v,ru,rv;
        while(m--)
        {
            scanf("%d%d",&u,&v);
            ru=find(u);
            rv=find(v);
            if(ru==rv) puts("-1");
            else
            {
                u=query(root[ru],1,tot);
                v=query(root[rv],1,tot);
                del(root[ru],u,1,tot);
                del(root[rv],v,1,tot);
                insert(root[ru],u/2,1,tot);
                insert(root[rv],v/2,1,tot);
                pa[ru]=rv;
                root[rv]=merge(root[ru],root[rv],1,tot);
                printf("%d\n",query(root[rv],1,tot));
            }
        }
    }
    return 0;
}

P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并

链接:https://www.luogu.com.cn/problem/P4556

题意:首先村落里的一共有 n 座房屋,并形成一个树状结构。然后救济粮分 mm 次发放,每次选择两个房屋 ( x , y ) (x, y) (x,y),然后对于 x 到 y 的路径上(含 x 和 y)每座房子里发放一袋 z 类型的救济粮。当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。

思路:树上差分前缀和,维护 sz 和 每个点的最大值。

  • 先访问到叶节点,然后差分回溯
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,tot=1e5;

int n,m;
vector<int> e[maxn];
int visit[maxn],ans[maxn];
int root[maxn],ls[maxn*70],rs[maxn*70],no;
struct ST
{
    int cnt,sz;
} st[maxn*70];

void update(int &rt,int pos,int L,int R,int val)
{
    if(!rt) rt=++no;
    if(L==R)
    {
        st[rt].sz+=val;
        st[rt].cnt+=val;
        return;
    }
    int mid=(L+R)>>1;
    if(pos<=mid) update(ls[rt],pos,L,mid,val);
    else update(rs[rt],pos,mid+1,R,val);
    st[rt].sz=st[ls[rt]].sz+st[rs[rt]].sz;
    st[rt].cnt=max(st[ls[rt]].cnt,st[rs[rt]].cnt);
}

int depth[maxn],fa[maxn][22];
void dfs1(int u,int f)
{
    depth[u]=depth[f]+1;
    fa[u][0]=f;
    for(int i=1; i<=20; ++i)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    for(auto v: e[u])
    {
        if(v==f) continue;
        dfs1(v,u);
    }
}

int lca(int u,int v)
{
    if(depth[u]<depth[v]) swap(u,v);
    int dis=depth[u]-depth[v];
    for(int i=0; (1<<i)<=dis; ++i)
        if(dis>>i&1) u=fa[u][i];
    if(u==v) return u;
    for(int i=20; i>=0; --i)
        if(fa[u][i]!=fa[v][i])
            u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}

int merge(int rt1,int rt2,int L,int R)
{
    if(!rt1||!rt2) return rt1?rt1:rt2;
    int rt=rt1;
    if(L==R)
    {
        st[rt].sz+=st[rt2].sz;
        st[rt].cnt+=st[rt2].cnt;
        return rt;
    }
    int mid=(L+R)>>1;
    ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
    rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);

    st[rt].sz=st[ls[rt]].sz+st[rs[rt]].sz;
    st[rt].cnt=max(st[ls[rt]].cnt,st[rs[rt]].cnt);
    return rt;
}
int query(int rt,int L,int R)
{
    if(!rt) return 0;
    if(L==R) return st[rt].cnt?L:0;
    int mid=(L+R)>>1;
    if(st[ls[rt]].cnt>=st[rs[rt]].cnt) return query(ls[rt],L,mid);
    else return query(rs[rt],mid+1,R);
}
void dfs2(int u,int f)
{
    visit[u]=1;
    for(auto v: e[u])
    {
        if(v==f) continue;
        dfs2(v,u);
        root[u]=merge(root[u],root[v],1,tot);
    }
    if(root[u]) ans[u]=query(root[u],1,tot);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n-1; ++i)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(1,0);
    int u,v,w;
    while(m--)
    {
        scanf("%d%d%d",&u,&v,&w);
        int x=lca(u,v);
        int fx=fa[x][0];
        update(root[u],w,1,tot,1);
        update(root[v],w,1,tot,1);
        update(root[x],w,1,tot,-1);
        if(fx) update(root[fx],w,1,tot,-1);
    }
    dfs2(1,0);
    for(int i=1; i<=n; ++i)
        printf("%d\n",ans[i]);
    return 0;
}

P1552 [APIO2012]派遣

链接:https://www.luogu.com.cn/problem/P1552

题意:给定一棵树,每个点有点权 c i c_i ci l i l_i li ,可以在每棵子树内选择最大的点数使得 c i c_i ci 之和不超过 m m m 。设选择的点数为 c n t cnt cnt ,问 l i × c n t l_i \times cnt li×cnt 的最大值是多少。

思路

  • 对每个点开一棵权值线段树:维护 cnt 和 sum ,表示当前值域区间的个数和区间和
  • 从叶到根,先合并当前点子树的所有信息,然后查询即可
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,m,tot;
ll C[maxn],L[maxn];
vector<int> allx,e[maxn];
int root[maxn],ls[maxn*40],rs[maxn*40],no;
struct Node
{
    int cnt;
    ll sum;
} st[maxn*40];
void update(int &rt,int pos,int L,int R)
{
    if(!rt) rt=++no;
    if(L==R)
    {
        st[rt].cnt++;
        st[rt].sum+=allx[pos-1];
        return;
    }
    int mid=(L+R)>>1;
    if(pos<=mid) update(ls[rt],pos,L,mid);
    if(pos>mid) update(rs[rt],pos,mid+1,R);
    st[rt].cnt=st[ls[rt]].cnt+st[rs[rt]].cnt;
    st[rt].sum=st[ls[rt]].sum+st[rs[rt]].sum;
}
int query(int rt,int L,int R,int val)
{
    if(!rt) return 0;
    if(L==R) return min(st[rt].cnt,val/allx[L-1]);
    int mid=(L+R)>>1;
    if(st[ls[rt]].sum>=val) return query(ls[rt],L,mid,val);
    else return st[ls[rt]].cnt+query(rs[rt],mid+1,R,val-st[ls[rt]].sum);
}
int merge(int rt1,int rt2,int L,int R)
{
    if(!rt1||!rt2) return rt1?rt1:rt2;
    int rt=rt1;
    if(L==R)
    {
        st[rt].cnt+=st[rt2].cnt;
        st[rt].sum+=st[rt2].sum;
        return rt;
    }
    int mid=(L+R)>>1;
    ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
    rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
    st[rt].cnt=st[ls[rt]].cnt+st[rs[rt]].cnt;
    st[rt].sum=st[ls[rt]].sum+st[rs[rt]].sum;
    return rt;
}
ll ans=0;
void dfs(int u)
{
    for(auto v: e[u])
    {
        dfs(v);
        root[u]=merge(root[u],root[v],1,tot);
    }
    if(root[u]) ans=max(ans,1ll*L[u]*query(root[u],1,tot,m));
}
int main()
{
    scanf("%d%d",&n,&m);
    int rt;
    for(int i=1; i<=n; ++i)
    {
        int u;
        scanf("%d%lld%lld",&u,&C[i],&L[i]);
        e[u].push_back(i);
        if(u==0) rt=i;
        allx.push_back(C[i]);
    }
    sort(allx.begin(),allx.end());
    allx.resize(unique(allx.begin(),allx.end())-allx.begin());
    tot=allx.size();
    for(int i=1; i<=n; ++i)
    {
        int pos=lower_bound(allx.begin(),allx.end(),C[i])-allx.begin()+1;
        update(root[i],pos,1,tot);
    }
    dfs(rt);
    printf("%lld\n",ans);
    return 0;
}

CF1009F Dominant Indices

链接:https://www.luogu.com.cn/problem/CF1009F

题意:给定一棵以 1 为根,n 个节点的树。设 d(u,x) 为 u 子树中到 u 距离为 x 的节点数。 对于每个点,求一个最小的 k,使得 d(u,k) 最大。

思路

  • 每个点根据深度建立权值线段树。查询深度出现次数最多的且最小的k。
  • 从叶到根,一遍更新答案一遍向上合并
  • 注意一下深度,根节点深度为 1 ,有 n 次更新,空间复杂度为 n l o g 2 ( m a x   d e p t h ) n log2(max~depth) nlog2(max depth)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+5,tot=1e6;
int n;
vector<int> e[maxn];
int depth[maxn],ans[maxn];
int root[maxn],ls[maxn*25],rs[maxn*25],st[maxn*25],no;
void update(int &rt,int pos,int L,int R)
{
    if(!rt) rt=++no;
    if(L==R)
    {
        st[rt]++;
        return;
    }
    int mid=(L+R)>>1;
    if(pos<=mid) update(ls[rt],pos,L,mid);
    else update(rs[rt],pos,mid+1,R);
    st[rt]=max(st[ls[rt]],st[rs[rt]]);
}
int query(int rt,int L,int R)
{
    if(!rt) return 0;
    if(L==R) return L;
    int mid=(L+R)>>1;
    if(st[ls[rt]]>=st[rs[rt]]) return query(ls[rt],L,mid);
    else return query(rs[rt],mid+1,R);
}
int merge(int rt1,int rt2,int L,int R)
{
    if(!rt1||!rt2) return rt1?rt1:rt2;
    int rt=rt1;
    if(L==R)
    {
        st[rt]+=st[rt2];
        return rt;
    }
    int mid=(L+R)>>1;
    ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
    rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
    st[rt]=max(st[ls[rt]],st[rs[rt]]);
    return rt;
}
void dfs(int u,int fa)
{
    depth[u]=depth[fa]+1;
    for(auto v: e[u])
    {
        if(v==fa) continue;
        dfs(v,u);
        root[u]=merge(root[u],root[v],1,tot);
    }
    update(root[u],depth[u],1,tot);
    ans[u]=query(root[u],1,tot);
}
int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n-1; ++i)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs(1,0);
    for(int i=1; i<=n; ++i)
        printf("%d\n",ans[i]-depth[i]);
    return 0;
}

P4197 Peaks

链接:https://www.luogu.com.cn/problem/P4197

题意:在 Bytemountains 有 n n n 座山峰,每座山峰有他的高度 h i h_i hi 。有些山峰之间有双向道路相连,共 m 条路径,每条路径有一个困难值 c i c_i ci,这个值越大表示越难走。 现在有 q q q 组询问,每组询问询问从点 v 开始只经过困难值小于等于 x 的路径所能到达的山峰中第 k k k 高的山峰,如果无解输出 − 1 -1 1

对于 100% 的数据, n ≤ 1 0 5 , 0 ≤ m , q ≤ 5 × 1 0 5 , h i , c , x ≤ 1 0 9 n \le 10^5,0 \le m,q \le 5\times 10^5,h_i,c,x \le 10^9 n1050m,q5×105hi,c,x109

思路

  • 边根据 w 排序,询问根据 x 排序。然后更新并查集、合并线段树。
  • 然后查询第 k 大就好了
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=5e5+5;

int n,m,q,tot;
int h[maxn],ans[maxm];
vector<int> allh;
struct Opt
{
    int v,x,k,id;
    bool operator<(const Opt &b) const
    {
        return x<b.x;
    }
} opt[maxm];
struct Edge
{
    int u,v,w;
    bool operator<(const Edge& b) const
    {
        return w<b.w;
    }
} e[maxm];

int pa[maxn],sz[maxn];
int find(int x)
{
    return x==pa[x]?x:pa[x]=find(pa[x]);
}
int root[maxn],ls[maxn*25],rs[maxn*25],st[maxn*25],no;

void update(int &rt,int pos,int L,int R)
{
    if(!rt) rt=++no;
    st[rt]++;
    if(L==R) return;
    int mid=(L+R)>>1;
    if(pos<=mid) update(ls[rt],pos,L,mid);
    else update(rs[rt],pos,mid+1,R);
}
int merge(int rt1,int rt2,int L,int R)
{
    if(!rt1||!rt2) return rt1|rt2;
    int rt=rt1;
    if(L==R)
    {
        st[rt]+=st[rt2];
        return rt;
    }
    int mid=(L+R)>>1;
    ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
    rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
    st[rt]=st[ls[rt]]+st[rs[rt]];
    return rt;
}
int query(int rt,int k,int L,int R)
{
    if(L==R) return L;
    int mid=(L+R)>>1;
    if(st[rs[rt]]>=k) return query(rs[rt],k,mid+1,R);
    else return query(ls[rt],k-st[rs[rt]],L,mid);
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1; i<=n; ++i)
    {
        scanf("%d",&h[i]);
        allh.push_back(h[i]);
        pa[i]=i;
        sz[i]=1;
    }
    sort(allh.begin(),allh.end());
    allh.resize(unique(allh.begin(),allh.end())-allh.begin());
    tot=allh.size();
    for(int i=1; i<=n; ++i)
    {
        int p=lower_bound(allh.begin(),allh.end(),h[i])-allh.begin()+1;
        update(root[i],p,1,tot);
    }
    for(int i=1; i<=m; ++i)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        e[i]= {u,v,w};
    }
    sort(e+1,e+1+m);
    for(int i=1; i<=q; ++i)
    {
        int v,x,k;
        scanf("%d%d%d",&v,&x,&k);
        opt[i]= {v,x,k,i};
    }
    sort(opt+1,opt+1+q);
    int pos=1;
    for(int i=1; i<=q; ++i)
    {
        while(pos<=m&&e[pos].w<=opt[i].x)
        {

            int u=e[pos].u,v=e[pos].v;
            int ru=find(u),rv=find(v);
            pos++;
            if(ru==rv) continue;
            pa[rv]=ru;
            sz[ru]+=sz[rv];
            root[ru]=merge(root[ru],root[rv],1,tot);
        }
        int v=opt[i].v;
        if(sz[find(v)]<opt[i].k) ans[opt[i].id]=-1;
        else
        {
            int p=query(root[find(v)],opt[i].k,1,tot);
            ans[opt[i].id]=allh[p-1];
        }
    }
    for(int i=1; i<=q; ++i)
        printf("%d\n",ans[i]);
    return 0;
}

P3521 [POI2011]ROT-Tree Rotations

链接:https://www.luogu.com.cn/problem/P3521

题意:给定一棵有 n 个叶节点的二叉树,每个叶节点带有权值 p_i ,构成了一个 n 的排列。保证除了叶节点之外的所有节点左右孩子都存在。现在你可以交换任意节点的左右孩子,交换任意次。问最终得到的叶节点前序遍历得到的排列的最小逆序对是多少。

思路

  • 每一个节点与节点直接是相互独立的。可以对每一个节点开一个权值线段树,在向上合并的过程中统计逆序对。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5,tot=2e5;

int n;

int ls[maxn*20],rs[maxn*20],st[maxn*20],no;

void update(int &rt,int pos,int L,int R)
{
    if(!rt) rt=++no;
    if(L==R)
    {
        st[rt]++;
        return;
    }
    int mid=(L+R)>>1;
    if(pos<=mid) update(ls[rt],pos,L,mid);
    else update(rs[rt],pos,mid+1,R);
    st[rt]=st[ls[rt]]+st[rs[rt]];
}
ll cnt1,cnt2,ans;
int merge(int rt1,int rt2,int L,int R)
{
    if(!rt1||!rt2) return rt1|rt2;
    int rt=rt1;
    if(L==R)
    {
        st[rt]+=st[rt2];
        return rt;
    }
    cnt1+=1ll*st[ls[rt1]]*st[rs[rt2]];
    cnt2+=1ll*st[rs[rt1]]*st[ls[rt2]];
    int mid=(L+R)>>1;
    ls[rt]=merge(ls[rt1],ls[rt2],L,mid);
    rs[rt]=merge(rs[rt1],rs[rt2],mid+1,R);
    st[rt]=st[ls[rt]]+st[rs[rt]];
    return rt;
}

void dfs(int &rt)
{
    int u=0,v=0,val;
    scanf("%d",&val);
    if(val==0)
    {
        dfs(u);
        dfs(v);
        cnt1=cnt2=0;
        rt=merge(u,v,1,tot);
        ans+=min(cnt1,cnt2);
    }
    else update(rt,val,1,tot);
}

int main()
{
    scanf("%d",&n);
    int rt=0;
    ans=0;
    dfs(rt);
    printf("%lld\n",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值