BZOJ[1018][SHOI2008]堵塞的交通traffic 线段树

传送门ber~

哇这个线段树好神啊!!

用线段树维护图连通性,每个节点开个二维数组 ai,j a i , j 表示这个区间最左面的第 i i 行能不能走到最右面的第j行,同时记录一下最左最右第一行与第二行是否都能互相走到
这六个东西可以用线段树维护啊(看代码)!

细节有点多…具体看代码吧…
注释说的很清楚(自信)

代码如下:

#include<algorithm>
#include<cstring>
#include<ctype.h>
#include<cstdio>
#define N 100050
using namespace std;
inline int read(){
    int x=0,f=1;char c;
    do c=getchar(),f=c=='-'?-1:f; while(!isdigit(c));
    do x=(x<<3)+(x<<1)+c-'0',c=getchar(); while(isdigit(c));
    return x*f;
}
bool b1[N],b2[N][3];///b1是纵向的边 b2是横向的边(向右指的)
struct Status{
    bool b[2][2],lb,rb;
    int l,r;
    inline void init(int x,int y){
        l=x;r=y;
        memset(b,false,sizeof b);
        lb=rb=false;
        return;
    }
    Status operator + (const Status k) const{
        Status tmp;
        tmp.l=l;tmp.r=k.r;
        tmp.lb=lb|(b2[r][1]&b2[r][2]&b[0][0]&b[1][1]&k.lb);
        ///合并当前状态和k状态,每个情况yy一下就非常好理解了
        tmp.rb=k.rb|(b2[r][1]&b2[r][2]&k.b[0][0]&k.b[1][1]&rb);
        tmp.b[0][0]=(b[0][0]&k.b[0][0]&b2[r][1])|(b[0][1]&k.b[1][0]&b2[r][2]);
        tmp.b[0][1]=(b[0][0]&k.b[0][1]&b2[r][1])|(b[0][1]&k.b[1][1]&b2[r][2]);
        tmp.b[1][0]=(b[1][0]&k.b[0][0]&b2[r][1])|(b[1][1]&k.b[1][0]&b2[r][2]);
        tmp.b[1][1]=(b[1][0]&k.b[0][1]&b2[r][1])|(b[1][1]&k.b[1][1]&b2[r][2]);
        return tmp;
    }
};
struct Node{
    Node *ls,*rs;
    Status a;///存的状态
    int l,r;
    inline void maintain(){
        if(l==r) return;
        a=ls->a+rs->a;
        return;
    }
}*root=new Node;
void maketree(int l,int r,Node *x){
    x->l=l;x->r=r;
    x->a.init(l,r);
    if(l==r){
        x->a.b[0][0]=x->a.b[1][1]=true;///建树,一定要将单个点的这个赋成true
        return;
    }
    int mid=(l+r)>>1;
    maketree(l,mid,x->ls=new Node);maketree(mid+1,r,x->rs=new Node);
    x->maintain();
    return;
}
void Modify_Up(int x,bool v,Node *k){///修改竖着的边,就是将线段树的单个节点的连通性修改一下
    if(k->l==k->r){
        k->a.b[0][1]=k->a.b[1][0]=v;
        k->a.lb=k->a.rb=v;
        return;
    }
    int mid=(k->l+k->r)>>1;
    if(x<=mid) Modify_Up(x,v,k->ls);
    else Modify_Up(x,v,k->rs);
    k->maintain();
    return;
}
void Update(int x,Node *k){///因为更改一条x到y的横着的边只会影响最多两串,只要到底部然后回溯时维护就好了
    if(k->l==k->r) return;
    int mid=(k->l+k->r)>>1;
    if(x<=mid) Update(x,k->ls);
    else Update(x,k->rs);
    k->maintain();
    return;
}
Status Query(int x,int y,Node *k){///询问,就是将所有子段的信息合并
    if(k->l>=x && k->r<=y){
        return k->a;
    }
    int mid=(k->l+k->r)>>1;
    if(y<=mid) return Query(x,y,k->ls);
    else if(x>mid) return Query(x,y,k->rs);
    else return Query(x,y,k->ls)+Query(x,y,k->rs);
}
int n,x,y,x1,y1;
char c[5];
bool ans;
int main(){
    n=read();
    maketree(1,n,root);
    while(""){
        scanf("%s",c+1);
        if(c[1]=='O'){
            x=read();y=read();x1=read();y1=read();
            if(y==y1){
                b1[y]=true;
                Modify_Up(y,true,root);///修改纵向的边,在线段树上修改
            }
            else{
                if(y>y1) swap(y,y1),swap(x,x1);///同下
                b2[y][x]=true;
                Update(y,root);
                Update(y1,root);
            }
        }
        else if(c[1]=='C'){
            x=read();y=read();x1=read();y1=read();
            if(y==y1){///同上
                b1[y]=false;
                Modify_Up(y,false,root);
            }
            else{
                if(y>y1) swap(y,y1),swap(x,x1);
                b2[y][x]=false;///修改横向的边,只要重新维护一遍就可以了
                Update(y,root);
                Update(y1,root);
            }
        }
        else if(c[1]=='A'){
            x=read();y=read();x1=read();y1=read();
            if(y>y1) swap(x,x1),swap(y,y1);
            Status l=Query(1,y,root),k=Query(y,y1,root),r=Query(y1,n,root);
            ///注意这里,l是左面的区域,目的是统计从左面绕过去的方案,r同理
            if(x==1 && x1==1) ans=k.b[0][0] | (l.rb & k.b[1][0]) | (r.lb & k.b[0][1]) | (r.lb & l.rb & k.b[1][1]);
            ///每个情况判断一下..还是挺恶心的
            if(x==1 && x1==2) ans=k.b[0][1] | (l.rb & k.b[1][1]) | (r.lb & k.b[0][0]) | (r.lb & l.rb & k.b[1][0]);
            if(x==2 && x1==1) ans=k.b[1][0] | (l.rb & k.b[0][0]) | (r.lb & k.b[1][1]) | (r.lb & l.rb & k.b[0][1]);
            if(x==2 && x1==2) ans=k.b[1][1] | (l.rb & k.b[0][1]) | (r.lb & k.b[1][0]) | (r.lb & l.rb & k.b[0][0]);
            puts(ans?"Y":"N");
        }
        else if(c[1]=='E') return 0;
    }
return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值