[BZOJ]1018 堵塞的交通

Description
有一天,由于某种穿越现象作用,你来到了传说中的小人国。小人国的布局非常奇特,整个国家的交通系统可
以被看成是一个2行C列的矩形网格,网格上的每个点代表一个城市,相邻的城市之间有一条道路,所以总共有2C个
城市和3C-2条道路。 小人国的交通状况非常槽糕。有的时候由于交通堵塞,两座城市之间的道路会变得不连通,
直到拥堵解决,道路才会恢复畅通。初来咋到的你决心毛遂自荐到交通部某份差事,部长听说你来自一个科技高度
发达的世界,喜出望外地要求你编写一个查询应答系统,以挽救已经病入膏肓的小人国交通系统。 小人国的交通
部将提供一些交通信息给你,你的任务是根据当前的交通情况回答查询的问题。交通信息可以分为以下几种格式:
Close r1 c1 r2 c2:相邻的两座城市(r1,c1)和(r2,c2)之间的道路被堵塞了;Open r1 c1 r2 c2:相邻的两座城
市(r1,c1)和(r2,c2)之间的道路被疏通了;Ask r1 c1 r2 c2:询问城市(r1,c1)和(r2,c2)是否连通。如果存在一
条路径使得这两条城市连通,则返回Y,否则返回N;
Input
  第一行只有一个整数C,表示网格的列数。接下来若干行,每行为一条交通信息,以单独的一行“Exit”作为
结束。我们假设在一开始所有的道路都是堵塞的。我们保证 C小于等于100000,信息条数小于等于100000。
Output
  对于每个查询,输出一个“Y”或“N”。
Sample Input
2
Open 1 1 1 2
Open 1 2 2 2
Ask 1 1 2 2
Ask 2 1 2 2
Exit
Sample Output
Y
N

线段树维护连通性

此等神题蒟蒻只得膜拜ORZ

我们将它按照平常的线段树考虑

先引入一个结论:如果我们知道了要求的两点之间那一大块的连通性(顶点间)和L左边和R右边的连通性,就可以求出L和R是否连通(具体如何求后面再说)下面考虑如何维护连通性

叶节点代表的是每一个竖着的宽度为0的块块(即一条竖线)

那么对于单个的块块来说我们很容易知道在节点内部的连通情况,即横着两个竖着两个斜着两个是否能够互相走到

叶节点初始就是斜着可以(从上到下或是从下到上)和竖着可以(同理)

只要考虑如何合并左儿子右儿子就可以得到整个的连通性(通过线段树Query也可以得到任意区间的连通性)

合并时就是一个这样的图形这里写图片描述

这时我们已经得到了左边的块和右边的块的连通性

考虑如何维护当前节点的连通性,下面横竖斜各举一例(另外一个反过来就行了XD)

这里写图片描述

横着有这两种情况 一种是左右儿子都横着可以连通,另一种是左儿子斜着可以,右儿子另外斜着可以(感觉在口胡)

这里写图片描述

竖着有蓝色和绿色两种情况(注意每一块都是处理过后的,也就是说右边的左边连通可能是从右边斜着更新过来的,所以这样就枚举到了所有的情况)

这里写图片描述

斜着有这两种情况,同样的,也枚举到了所有的情况

特别的,我们需要知道能不能从mid走到mid + 1,如果不能的话以上就都不能实现,所以我们要记录一下 t[i][j] 表示i行j列的右边能不能连通

有了合并的方式这个问题其实就解决了,剩下的就是各种细节乱搞了~~

现在考虑已知连通性后如何求出需要的连通情况

假设要求的是s列到t列的某行到某行的两个点是否连通

我们要得到1到s的节点,s到t的节点,t到n的节点(具体为什么一会说)

在这里要分成在同一行和在不同行

同一行:两种:一种是直接s到t的节点当前行横向连通就可以
另一种是1到s的节点右边竖向连通,s到t的节点另一行横向连通,t到n的节点左边竖向连通(即从左边绕一下到另一行再从右边绕回来)
不同行:四种(好可怕):
  1. s到t的节点当前行斜着连通(直接走到另一个点)
  2. s到t的节点当前行横着连通,t到n的节点左边竖向连通(先横着走一下,在从右边绕回来)
  3. 1到n的节点右边竖向连通,s到t的节点另一行横向连通(先从左边绕到另一行再横着走过去)
  4. 1到n的节点右边竖向连通,s到t的节点另一条对角线连通(即不与左边要求的点相连的对角线),t到n的节点左边竖向连通(从左边绕到另一行,在当前节点斜着走到当前行,再从右边绕到另一行)

这样问题解决,具体看代码

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

#define mid (l + r) / 2
#define rc (o * 2 + 1)
#define lc (o * 2)

using namespace std;

const int Maxn = 100100;

int n,m;
char s[8];
struct node {bool u[2],v[2],a[2];}a[1<<18];
bool t[2][Maxn];//维护横向连通性 

node Merge(node f,node s,int l,int r,int o){
    node x[] = {f,s},A;
    for(int i = 0;i <= 1;i++){
        A.u[i] = (x[0].u[i] && t[i][mid] && x[1].u[i]) ||
        (x[0].a[i] && t[i ^ 1][mid] && x[1].a[i ^ 1]);
        //两种选择
        A.v[i] = (x[i].v[i]) || 
        (x[i].u[0] && x[i].u[1] && t[0][mid] && t[1][mid] && x[i ^ 1].v[i]); 
        //同样两种选择
        A.a[i] = (x[0].u[i] && t[i][mid] && x[1].a[i]) ||
        (x[0].a[i] && t[i ^ 1][mid] && x[1].u[i ^ 1]);//同样 
    }return A; 
}

node Get(int s,int t,int l,int r,int o){
    if(s == l && t == r)return a[o];
    if(t <= mid)return Get(s,t,l,mid,lc);
    if(s >  mid)return Get(s,t,mid + 1,r,rc);
    return Merge(Get(s,mid,l,mid,lc),Get(mid + 1,t,mid + 1,r,rc),l,r,o);
}

bool Query(int s,int t,int i,int j){
    node a = Get(1,s,1,n,1),b = Get(s,t,1,n,1),c = Get(t,n,1,n,1);
    return i == j ? ( b.u[i] || a.v[1] && b.u[i ^ 1] && c.v[0]) :
    ((b.u[i] && c.v[0]) || (a.v[1] && b.u[j]) || (b.a[i])
    || a.v[1] && b.a[j] && c.v[0]);
}

void Build(int l,int r,int o){
    if(l == r)memset(a[o].u,1,2);
    else {Build(l,mid,lc);Build(mid + 1,r,rc);} 
}

void Update(int l,int r,int o){a[o] = Merge(a[lc],a[rc],l,r,o);}

void ChangeSame(int l,int r,int o,int b,int p){
    if(l == r){memset(a[o].v,p,2);memset(a[o].a,p,2);}
    else {
        if(b <= mid)ChangeSame(l,mid,lc,b,p);
        if(b >  mid)ChangeSame(mid + 1,r,rc,b,p);
        Update(l,r,o);  
    }
}

void ChangeDif(int l,int r,int o,int u){
    if(l == r)return;
    if(u <= mid)ChangeDif(l,mid,lc,u);
    if(u >  mid)ChangeDif(mid + 1,r,rc,u);
    Update(l,r,o);
}

int main(){
    scanf("%d",&n);Build(1,n,1);
    while(scanf("%s",s) != EOF && s[0] != 'E'){
        int x1,y1,x2,y2;scanf("%d%d%d%d",&x1,&y1,&x2,&y2);x1--;x2--;
        if(y1 > y2)swap(y1,y2),swap(x1,x2);
        if(s[0] == 'A'){printf("%c\n",Query(y1,y2,x1,x2) ? 'Y' : 'N');}
        else {
            if(x1 != x2)ChangeSame(1,n,1,y1,s[0] == 'O');
            else {t[x1][y1] = s[0] == 'O';ChangeDif(1,n,1,y1);} 
        }
    }return 0;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值