【XSY2528】道路建设 LCT 可持久化线段树

题目描述

  给你一个\(n\)个点\(m\)条边图,\(q\)个询问,每次问你边权在\([l,r]\)之间的边组成的最小生成树(森林)的边权和。强制在线。

  \(n,m,q\leq 100000\)

题解

  考虑离线做法。从大到小加边,用LCT维护当前的最小生成树。维护一棵线段树,第\(i\)个位置表示当前的最小生成树中边权为\(i\)的边的权值和。当一条边被加入时就在对应位置加上边权,删掉时就减掉边权。假设已经处理了边权\(\geq i\)的所有边,那么对于所有\(l=i\)的询问的答案就是线段树中\([1,r]\)的数和(等价于\([l,r]\),因为\([1,l-1]\)都是空的)。

  把这棵线段树变成可持久化线段树就可以在线处理询问了。

  我也不知道为什么边权范围是\([1,10000]\)

  时间复杂度:\(O((m+q)\log n)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
namespace lct
{
    int f[200010];
    int a[200010][2];
    pii s[200010];
    pii v[200010];
    int rev[200010];
    void reverse(int x)
    {
        rev[x]^=1;
        swap(a[x][0],a[x][1]);
    }
    void push(int x)
    {
        if(rev[x])
        {
            if(a[x][0])
                reverse(a[x][0]);
            if(a[x][1])
                reverse(a[x][1]);
            rev[x]=0;
        }
    }
    void mt(int x)
    {
        s[x]=v[x];
        if(a[x][0])
            s[x]=max(s[x],s[a[x][0]]);
        if(a[x][1])
            s[x]=max(s[x],s[a[x][1]]);
    }
    int root(int x)
    {
        return !f[x]||(a[f[x]][0]!=x&&a[f[x]][1]!=x);
    }
    void rotate(int x)
    {
        if(root(x))
            return;
        int p=f[x];
        int q=f[p];
        int ps=(x==a[p][1]);
        int qs=(p==a[q][1]);
        int ch=a[x][ps^1];
        if(!root(p))
            a[q][qs]=x;
        a[x][ps^1]=p;
        a[p][ps]=ch;
        if(ch)
            f[ch]=p;
        f[p]=x;
        f[x]=q;
        mt(p);
        mt(x);
    }
    void pushdown(int x)
    {
        if(!root(x))
            pushdown(f[x]);
        push(x);
    }
    void splay(int x)
    {
        pushdown(x);
        while(!root(x))
        {
            int p=f[x];
            if(!root(p))
            {
                int q=f[p];
                if((x==a[p][1])^(p==a[q][1]))
                    rotate(x);
                else
                    rotate(p);
            }
            rotate(x);
        }
    }
    void access(int x)
    {
        int y=x,t=0;
        while(x)
        {
            splay(x);
            a[x][1]=t;
            mt(x);
            t=x;
            x=f[x];
        }
        splay(y);
    }
    void change(int x)
    {
        access(x);
        reverse(x);
    }
    void link(int x,int y)
    {
        change(x);
        f[x]=y;
    }
    void cut(int x,int y)
    {
        change(x);
        access(y);
        f[a[y][0]]=0;
        a[y][0]=0;
    }
    pii query(int x,int y)
    {
        change(x);
        access(y);
        return s[y];
    }
    int findroot(int x)
    {
        access(x);
        while(a[x][0])
            x=a[x][0];
        splay(x);
        return x;
    }
}
namespace seg
{
    int n=0;
    int ls[5000010];
    int rs[5000010];
    int s[5000010];
    int rt[100010];
    int change(int p1,int x,int v,int l,int r)
    {
        int p=++n;
        ls[p]=ls[p1];
        rs[p]=rs[p1];
        s[p]=s[p1]+v;
        if(l==r)
            return p;
        int mid=(l+r)>>1;
        if(x<=mid)
            ls[p]=change(ls[p1],x,v,l,mid);
        if(x>mid)
            rs[p]=change(rs[p1],x,v,mid+1,r);
        return p;
    }
    int query(int p,int l,int r,int L,int R)
    {
        if(l<=L&&r>=R)
            return s[p];
        int mid=(L+R)>>1;
        int res=0;
        if(l<=mid)
            res+=query(ls[p],l,r,L,mid);
        if(r>mid)
            res+=query(rs[p],l,r,mid+1,R);
        return res;
    }
}
struct edge
{
    int x,y,d;
};
int cmp(edge a,edge b)
{
    return a.d<b.d;
}
edge a[100010];
int main()
{
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);
    int n,m,on;
    scanf("%d%d%d",&n,&m,&on);
    int i;
    for(i=1;i<=m;i++)
        scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].d);
    sort(a+1,a+m+1,cmp);
    int j=m;
    seg::rt[n+1]=0;
    for(i=1;i<=n;i++)
        lct::v[i]=pii(0,0);
    for(i=10000;i>=1;i--)
    {
        seg::rt[i]=seg::rt[i+1];
        while(j>=1&&a[j].d==i)
        {
            int rx=lct::findroot(a[j].x);
            int ry=lct::findroot(a[j].y);
            if(rx==ry)
            {
                pii s=lct::query(a[j].x,a[j].y);
                if(s.first>a[j].d)
                {
                    lct::cut(a[s.second].x,s.second+n);
                    lct::cut(a[s.second].y,s.second+n);
                    lct::v[j+n]=pii(a[j].d,j);
                    lct::link(a[j].x,j+n);
                    lct::link(a[j].y,j+n);
                    seg::rt[i]=seg::change(seg::rt[i],s.first,-s.first,1,10000);
                    seg::rt[i]=seg::change(seg::rt[i],a[j].d,a[j].d,1,10000);
                }
            }
            else
            {
                lct::v[j+n]=pii(a[j].d,j);
                lct::link(a[j].x,j+n);
                lct::link(a[j].y,j+n);
                seg::rt[i]=seg::change(seg::rt[i],a[j].d,a[j].d,1,10000);
            }
            j--;
        }
    }
    int q;
    scanf("%d",&q);
    int l,h,last=0;
    for(i=1;i<=q;i++)
    {
        scanf("%d%d",&l,&h);
        l-=on*last;
        h-=on*last;
        last=seg::query(seg::rt[l],1,h,1,10000);
        printf("%d\n",last);
    }
    return 0;
}

转载于:https://www.cnblogs.com/ywwyww/p/8513137.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值