您的位置:首页 > 其它

bzoj3995 [SDOI2015]道路修建

2018-02-07 19:06 197 查看
题目链接

分析:

曲神的题解

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

bzoj1018的进阶版

一开始看到这道题:好像不是很难,用线段树维护最小生成树

update的时候直接判断上下哪一条横边比较小,连接即可

然而一下就发现了bug:

存在可能性连接点的上下两条边都需要连接

那我们就要深入讨论

代码中的区间划分方式和平成不大一样:

[l.r]−−−>[l.mid]+[mid,r][l.r]−−−>[l.mid]+[mid,r]

之后的讨论都是基于以上条件

这样有一个好处:

每个结点维护的是一个2*2的小图,方便处理线段树结点中的信息(之后会介绍)

当然朴素的线段树那样分割也是可以的

我们在update之前左右区间都是一个完美的联通块

如果出现了上述情况,连接上下的两条边后会出现环

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

然而这个删除并没有表面上(记录最大值删除)这么简单

(难道这就是墨菲定律的体现???)

试想如果我们在每一个结点中记录了一个最大值,每次合并的时候删除了,那我们去哪里找次大值作为下次删除的边呢

我们先来看个图:



我们在合并左右区间的时候,如果得到环,那么一定是上面标记出的最小环

所以我们需要删除的边一定在这个最小环上

怎么确定这个环呢?

我们需要记录每个结点中最靠左的竖线和最靠右的竖线:



显然,最左竖线再往左的所有横线一定都被选中

最有竖线再往右的所有横线一定都被选中

在合并区间的时就可以用左区间的→_→竖线和右区间的最左竖线确定这个环

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

如果是横边(绿色),直接删掉



如果是竖边,删掉后想一下对于这个大区间“最靠左的竖线和最靠右的竖线”是否发生了改变

这样线段树的及诶单中就需要维护一下几个值:

mxmx:区间最大边权

lsls:最靠左的竖线

rsrs:最靠右的竖线

lmxlmx:最靠左的竖线再往左的横线中的最小边权(方便查找合并时查找最小环中的最小边权)

rmxrmx:最靠右的竖线再往右的横线中的最小边权(方便查找合并时查找最小环中的最小边权)

sumsum:最小生成树的权值和

维护的时候,当:l+1=rl+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
,v2
,v
,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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: