首页 > 学院 > 开发设计 > 正文

[BZOJ1018][SHOI2008]堵塞的交通traffic(线段树)

2019-11-08 01:47:56
字体:
来源:转载
供稿:网友

=== ===

这里放传送门

=== ===

题解

这题有毒。。。。据说zyf2000写了7k代码结果T了然后又写了一个。。。

考虑把每个小矩形看做序列中的一个元素,然后对每个区间维护六个量,如下图所示(妈呀这可怕的分辨率): 这里写图片描述

线段树里面每一个元素的含义就是在当前节点管辖的矩形序列内部是否存在一条和x0(或其它编号)等价的路径。比如线段树中代表y0的元素为true就代表存在一条从这个区间的左上角走到这个区间的左下角的路径。并且注意到这个东西是可以合并的,也就是说如果把一排小矩形构成的序列以mid为界(这里mid是指两个点构成的一条竖直的线而不是一个小矩形)分成两部分,从序列的左端走到右端或从右端走到左端必然会经过mid这条线,那么就可以左右子树合并来得到父亲的值。

举个例子来说,如果要递推当前节点的x0,有两种方法,一是经过mid这条线段的上端点,一是经过mid这条线段的下端点。第一种情况要求左右子树的x0都联通,第二种情况要求左子树的z0和右子树的z1联通。

如果是线段树的叶子节点的话就只能手动枚举每一种情况了。。。比如上面那个图如果要求x0联通,一种方法是AB那条路直接就是通的,还有一种方法是先走y0再走z1,或者y0->x1->y1,或者z0->y1,或者z0->x1->z1。其他的部分情况也超级多啊一定要想全。。。

注意因为线段树数组里表示的是连通性,而有些时候Close操作的改动可能比较麻烦,举个例子来说,如果在一个单位矩形中联通了x0,y0和x1三条边,那么通过UPD过程会把它所有的6个域都置成true,因为四个点中任意两个都能相互到达。而如果下一步去掉了y0边,按理说这时只有x0和x1能联通了,但线段树中去掉y0以后剩下的5个域都是true,程序会理解为有5条边都联通,然后把对于y0的修改覆盖掉。于是就另开了一个数组存了不加递推的原始的边的情况,在修改的时候一并维护。

查询操作也非常麻烦。。因为[x..y]这一段的连通性可能不止与[x..y]这一段路径有关,可能它会上别的地方去绕一圈再回来,简单来说就是可以直接到达,也可以从左边拐出序列,也可以从右边拐出序列,也可以左右同时拐出序列。一条路径最多有四种不同的走法,都要考虑进去。举例来说,如果矩形序列[left..right]的x0要联通,那么一种方法是直接在序列内部从左上角走到右上角,一种方法是先从区间[1..left-1]的右上角走到右下角,即顺着y1边走,然后再从[left..right]的x1边走,然后从[left..right]的y1边走到终点,还有一种方法是先顺着[left..right]的y0边走,然后走x1边,然后顺着[right+1..c]的y0边走等等。

反正这题就是有毒。。。

代码

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int c,cnt;char o[10];struct tree{ bool b[3][2]; tree(){memset(b,false,sizeof(b));} void UPD(tree g);}t[500010],ans,org[100010];void tree::UPD(tree g){//通过边的信息枚举单位矩形的联通状况 b[0][0]=g.b[0][0]||(g.b[1][0]&&g.b[2][1])||(g.b[1][0]&&g.b[0][1]&&g.b[1][1])||(g.b[2][0]&&g.b[1][1])||(g.b[2][0]&&g.b[0][1]&&g.b[2][1]); b[0][1]=g.b[0][1]||(g.b[1][0]&&g.b[2][0])||(g.b[1][0]&&g.b[0][0]&&g.b[1][1])||(g.b[2][1]&&g.b[1][1])||(g.b[2][1]&&g.b[0][0]&&g.b[2][0]); b[1][0]=g.b[1][0]||(g.b[2][0]&&g.b[0][1])||(g.b[2][0]&&g.b[1][1]&&g.b[2][1])||(g.b[0][0]&&g.b[2][1])||(g.b[0][0]&&g.b[1][1]&&g.b[0][1]); b[1][1]=g.b[1][1]||(g.b[2][1]&&g.b[0][1])||(g.b[2][1]&&g.b[1][0]&&g.b[2][0])||(g.b[0][0]&&g.b[2][0])||(g.b[0][0]&&g.b[1][0]&&g.b[0][1]); b[2][0]=g.b[2][0]||(g.b[0][0]&&g.b[1][1])||(g.b[0][0]&&g.b[2][1]&&g.b[0][1])||(g.b[1][0]&&g.b[0][1])||(g.b[1][0]&&g.b[2][1]&&g.b[1][1]); b[2][1]=g.b[2][1]||(g.b[1][0]&&g.b[0][0])||(g.b[1][0]&&g.b[2][0]&&g.b[1][1])||(g.b[0][1]&&g.b[1][1])||(g.b[0][1]&&g.b[2][0]&&g.b[0][0]);}void update(tree &tr,tree lc,tree rc){ tr.b[0][0]=(lc.b[0][0]&&rc.b[0][0])||(lc.b[2][0]&&rc.b[2][1]); tr.b[0][1]=(lc.b[0][1]&&rc.b[0][1])||(lc.b[2][1]&&rc.b[2][0]); tr.b[1][0]=lc.b[1][0]||(lc.b[0][0]&&lc.b[0][1]&&rc.b[1][0]); tr.b[1][1]=rc.b[1][1]||(rc.b[0][0]&&rc.b[0][1]&&lc.b[1][1]); tr.b[2][0]=(lc.b[0][0]&&rc.b[2][0])||(lc.b[2][0]&&rc.b[0][1]); tr.b[2][1]=(lc.b[2][1]&&rc.b[0][0])||(lc.b[0][1]&&rc.b[2][1]);}void change(int i,int l,int r,int u,int x,int y,int v){ if (l==r){ org[l].b[x][y]=v;//注意:直接修改的只有org数组 t[i].UPD(org[l]); return; } int mid=(l+r)>>1; if (u<=mid) change(i<<1,l,mid,u,x,y,v); else change((i<<1)+1,mid+1,r,u,x,y,v); update(t[i],t[i<<1],t[(i<<1)+1]);}tree ask(int i,int l,int r,int left,int right){ tree ans[4]; int cnt=0; if ((left<=l)&&(right>=r)) return t[i]; int mid=(l+r)>>1; if (left<=mid){ ans[2]=ask(i<<1,l,mid,left,right);cnt+=1; }//注意用cnt变量记录访问信息 if (right>mid){ ans[3]=ask((i<<1)+1,mid+1,r,left,right);cnt+=2; } if (cnt==3){//只有两半区间都访问过才合并 update(ans[1],ans[2],ans[3]); return ans[1]; }else return ans[cnt+1];}bool query(int left,int right,int x,int y){ ans=ask(1,1,c,left,right); return ans.b[x][y];}int main(){ scanf("%d",&c);--c; scanf("%s",o); while (o[0]!='E'){ int r1,c1,r2,c2; scanf("%d%d%d%d",&r1,&c1,&r2,&c2); if ((o[0]=='O')||(o[0]=='C')){ int dlt,x,y,num=min(c1,c2); if ((r1==1)&&(r2==1)) x=y=0; if ((r1==2)&&(r2==2)) {x=0;y=1;}//如果是横向边,找到要修改的编号 if (o[0]=='O') dlt=1; else dlt=0; if (c1!=c2) change(1,1,c,num,x,y,dlt); if (c1==c2) if (c1==c+1) change(1,1,c,c,1,1,dlt); else if (c1==1) change(1,1,c,1,1,0,dlt); else{//注意在修改一条纵向边的时候会改变两个矩形,分别是y1和y0域。 change(1,1,c,c1,1,0,dlt); change(1,1,c,c1-1,1,1,dlt); } }else{ if ((r1==r2)&&(c1==c2)){ PRintf("Y/n"); scanf("%s",o); continue; } bool can=0; int x; if (r1==r2){ if (c1>c2) swap(c1,c2);//保证一定是编号小的在前 if (r1==1) x=0; else x=1; if (c1==1&&c2==c+1) can=query(1,c,0,x); else//特判边界的情况,防止越界 if (c1==1) can=query(c1,c2-1,0,x)||(query(c1,c2-1,2,x)&&query(c2,c,1,0)); else//如果左端点是1,那么只能从右边拐出去,其它同理 if (c2==c+1) can=query(c1,c2-1,0,x)||(query(c1,c2-1,2,x^1)&&query(1,c1-1,1,1)); else{ bool can1,can2,can3; can1=query(c1,c2-1,0,x)||(query(1,c1-1,1,1)&&query(c1,c2-1,2,x^1)); can2=query(c2,c,1,0)&&query(c1,c2-1,2,x); can3=query(1,c1-1,1,1)&&query(c1,c2-1,0,x^1)&&query(c2,c,1,0); can=can1||can2||can3; } }else if (c1==c2) if (c1==1) can=query(1,c,1,0); else if (c1==c+1) can=query(1,c,1,1); else can=query(1,c1-1,1,1)||query(c1,c,1,0); else{ if (c1>c2){ swap(r1,r2);swap(c1,c2); } if (r1<r2) x=0; else x=1; if (c1==1&&c2==c+1) can=query(1,c,2,x); else if (c1==1) can=query(c1,c2-1,2,x)||(query(c1,c2-1,0,x)&&query(c2,c,1,0)); else if (c2==c+1) can=query(c1,c2-1,2,x)||(query(c1,c2-1,0,x^1)&&query(1,c1-1,1,1)); else{ bool can1,can2,can3; can1=query(c1,c2-1,2,x)||(query(c1,c2-1,0,x)&&query(c2,c,1,0)); can2=query(c1,c2-1,0,x^1)&&query(1,c1-1,1,1); can3=query(1,c1-1,1,1)&&query(c1,c2-1,2,x^1)&&query(c2,c,1,0); can=can1||can2||can3; } } printf("%c/n",(can==0)?'N':'Y'); } scanf("%s",o); } return 0;}

偏偏在最后出现的补充说明

这种很麻烦的题一定要想清楚再写要不然会花费大量的时间调试。。 原来我以前写代码就这么蠢啊。。。 我还以为是最近才染上总是会把题写麻烦的毛病呢= =


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表