bzoj3995 [SDOI2015]道路修建

题目链接

分析:
曲神的题解
(曲神表示想要结构体版本的代码,题解最后给出的就是结构体的代码)

bzoj1018的进阶版

一开始看到这道题:好像不是很难,用线段树维护最小生成树
update的时候直接判断上下哪一条横边比较小,连接即可

然而一下就发现了bug:
存在可能性连接点的上下两条边都需要连接
那我们就要深入讨论

代码中的区间划分方式和平成不大一样:
[l.r]>[l.mid]+[mid,r] [ l . r ] − − − > [ l . m i d ] + [ m i d , r ]
之后的讨论都是基于以上条件

这样有一个好处:
每个结点维护的是一个2*2的小图,方便处理线段树结点中的信息(之后会介绍)
当然朴素的线段树那样分割也是可以的

我们在update之前左右区间都是一个完美的联通块
如果出现了上述情况,连接上下的两条边后会出现环

那我们就遵循最小生成树的原则:删除环上的最长边

然而这个删除并没有表面上(记录最大值删除)这么简单
(难道这就是墨菲定律的体现???)

试想如果我们在每一个结点中记录了一个最大值,每次合并的时候删除了,那我们去哪里找次大值作为下次删除的边呢
我们先来看个图:
这里写图片描述
我们在合并左右区间的时候,如果得到环,那么一定是上面标记出的最小环
所以我们需要删除的边一定在这个最小环上

怎么确定这个环呢?
我们需要记录每个结点中最靠左的竖线和最靠右的竖线:
这里写图片描述

显然,最左竖线再往左的所有横线一定都被选中
最有竖线再往右的所有横线一定都被选中
在合并区间的时就可以用左区间的最右竖线和右区间的最左竖线确定这个环

那现在我们就需要在这个环上选一条边删掉:

  • 如果是横边(绿色),直接删掉
    这里写图片描述
  • 如果是竖边,删掉后想一下对于这个大区间“最靠左的竖线和最靠右的竖线”是否发生了改变
这样线段树的结点中就需要维护一下几个值:

mx m x :区间最大边权
ls l s :最靠左的竖线
rs r s :最靠右的竖线
lmx l m x :最靠左的竖线再往左的横线中的最小边权(方便查找合并时查找最小环中的最小边权)
rmx r m x :最靠右的竖线再往右的横线中的最小边权(方便查找合并时查找最小环中的最小边权)
sum s u m :最小生成树的权值和

维护的时候,当: l+1=r l + 1 = r ,我们就单点插入
修改的时候,不管是横线还是竖线,我们不用纠结到底这个修改在哪里,
因为修改一条边会对整个区间都产生影响,所以直接:

change(bh<<1,l,mid,x,min(mid,y));
change(bh<<1|1,mid,r,max(x,mid+1),y);

tip

经历了一个不短的WA过程
发现是在修改的时候v1,v2数组改错了。。。
询问的时候,如果L=R,应该输出竖线的值

线段树的写法有很多种,有时候可以考虑使每个最小结点维护一个小区间

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N=100010;
struct node{
    int ls,rs,mx,lmx,rmx,sum;
};
node t[N<<2];
int v1[N],v2[N],v[N],n,m;

node insert(int l,int r)
{
    node ans;
    ans.mx=max(v1[l],v2[l]);     //横线最大值
    if (ans.mx>=max(v[l],v[r]))  //竖线
    {
        ans.ls=l; ans.rs=r;      //最左最右竖线 
        ans.lmx=ans.rmx=0;       //最左竖线左边(最右竖线右边)的横线最大值
        ans.sum=v[l]+v[r]+min(v1[l],v2[l]);
    }
    else if (v[l]>v[r])         //选择右边的竖线 
    {
        ans.ls=ans.rs=r;
        ans.lmx=ans.mx; ans.rmx=0;
        ans.sum=v[r]+v1[l]+v2[l];   
    }    
    else
    {
        ans.ls=ans.rs=l;         //选择左边的竖线    
        ans.lmx=0; ans.rmx=ans.mx;
        ans.sum=v[l]+v1[l]+v2[l];   
    }  
    return ans;   
}

node update(node l,node r)
{
    node ans;
    ans.mx=max(l.mx,r.mx);
    ans.sum=l.sum+r.sum;
    ans.ls=l.ls; ans.rs=r.rs;
    ans.lmx=l.lmx; ans.rmx=r.rmx;
    int mxx=max(l.rmx,r.lmx);            //最小环上的最大横边 
    if (mxx>=max(v[l.rs],v[r.ls]))       //直接删除 
        ans.sum-=mxx;
    else if (v[l.rs]>v[r.ls])            //删除左竖线 
    {
        ans.sum-=v[l.rs];
        if (l.rs==l.ls)                  //只有一条竖线
            ans.ls=r.ls,ans.lmx=max(l.mx,r.lmx);
    }
    else 
    {
        ans.sum-=v[r.ls];
        if (r.ls==r.rs)
            ans.rs=l.rs,ans.rmx=max(r.mx,l.rmx);
    }
    return ans;
}

void build(int bh,int l,int r)
{
    if (l+1==r)
    {
        t[bh]=insert(l,r);
        return;
    }
    int mid=(l+r)>>1;
    build(bh<<1,l,mid);
    build(bh<<1|1,mid,r);
    t[bh]=update(t[bh<<1],t[bh<<1|1]);
}

node ask(int bh,int l,int r,int x,int y)
{
    if (l==x&&r==y) return t[bh];
    int mid=(l+r)>>1;
    if (y<=mid) return ask(bh<<1,l,mid,x,y);
    else if (x>=mid) return ask(bh<<1|1,mid,r,x,y);
    else {
        node A=ask(bh<<1,l,mid,x,mid);
        node B=ask(bh<<1|1,mid,r,mid,y);
        return update(A,B);
    }
}

void change(int bh,int l,int r,int x,int y)
{
    if (x>y) return;
    if (l+1==r)
    {
        t[bh]=insert(l,r);
        return;
    }
    int mid=(l+r)>>1;
    change(bh<<1,l,mid,x,min(mid,y));
    change(bh<<1|1,mid,r,max(x,mid),y);
    t[bh]=update(t[bh<<1],t[bh<<1|1]);
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<n;i++) scanf("%d",&v1[i]);
    for (int i=1;i<n;i++) scanf("%d",&v2[i]);
    for (int i=1;i<=n;i++) scanf("%d",&v[i]);

    build(1,1,n);

    char s[5]; int xa,ya,xb,yb,z;
    for (int i=1;i<=m;i++)
    {
        scanf("%s%d%d",s,&xa,&ya);
        if (s[0]=='C') 
        {
            scanf("%d%d%d",&xb,&yb,&z);
            if (xa>xb) swap(xa,xb);
            if (ya>yb) swap(ya,yb);
            if (xa==xb)                    //横线 
            {
                if (xa==1) v1[ya]=z;
                else v2[ya]=z;
            } 
            else v[ya]=z;                  //竖线 
            change(1,1,n,ya,yb); 
        }
        else
        {
            if (xa==ya) printf("%d\n",v[xa]);
            else printf("%d\n",ask(1,1,n,xa,ya).sum);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值