题解 P4246 【[SHOI2008]堵塞的交通】
AThousandSuns · · 题解
线段树神题……没想到线段树还有这种玩法……
打出这道题对线段树的理解和运用一定有巨大帮助。
这题一看像是LCT的样子,然而LCT还得支持各种奇怪的东西,还有巨大常数……一不小心就会挂。
考虑线段树。咋啥都能考虑
一个节点维护
(u,d,l,r,p,q都是bool。下图中有个错别字,懒得改了)
对于叶子节点,很明显:
本题最恶心也是最厉害的地方来了:pushup。
先看看l。
r同理。这一部分这样写:
//conn[x][0]表示(1,x)和(1,x+1)是否联通
//conn[x][1]表示(2,x)和(2,x+1)是否联通
x.l=l.l|(l.u&conn[l.rig][0]&r.l&conn[l.rig][1]&l.d);
//左边能直接走下去
//左边能走上面,中上联通,再通过右边走到下面,中下联通,再通过左边走下面绕回来
x.r=r.r|(r.u&conn[l.rig][0]&l.r&conn[l.rig][1]&r.d);
//同理
再看看u。
d同理。这一部分这样写:
x.u=(l.u&conn[l.rig][0]&r.u)|(l.p&conn[l.rig][1]&r.q);
//左边能走上面,中上联通,右边能走上面
//左边能走主对角线,中下联通,右边能走副对角线
x.d=(l.d&conn[l.rig][1]&r.d)|(l.q&conn[l.rig][0]&r.p);
//同理
最后还有p:
q同理。最后放出整个pushup代码:
//pushup这样传参的原因待会会说
void pushup(node &x,node l,node r){
x.lft=l.lft; //x的左边界,因为conn要用到所以也要pushup
x.rig=r.rig; //x的右边界
x.l=l.l|(l.u&conn[l.rig][0]&r.l&conn[l.rig][1]&l.d);
x.r=r.r|(r.u&conn[l.rig][0]&l.r&conn[l.rig][1]&r.d);
x.u=(l.u&conn[l.rig][0]&r.u)|(l.p&conn[l.rig][1]&r.q);
x.d=(l.d&conn[l.rig][1]&r.d)|(l.q&conn[l.rig][0]&r.p);
x.p=(l.u&conn[l.rig][0]&r.p)|(l.p&conn[l.rig][1]&r.d);
x.q=(l.d&conn[l.rig][1]&r.q)|(l.q&conn[l.rig][0]&r.u);
}
现在来写修改操作。
我们发现在同一行和不在同一行有很多区别(要改的东西不一样),那就分开讨论。
先看不在同一行:(即同一列)
对于这一列代表的叶子节点,实际上就是上面和下面的连通性发生了变化。
然后沿途pushup就行了。
在同一行的会比较难懂。我也是看了别的题解才明白……
发现对于
我们发现,修改
会怎么影响呢?其实就是conn会变,然后就会影响整个节点的联通性。
那么修改完conn之后,对这个节点重新pushup一遍就好了。
这里贴个代码:
//o:节点编号,l:左端点,r:右端点
//p,row:表示修改(row+1,p)和(row+1,p+1),因为第一行在conn是0,第二行是1
//val:开还是关
void modify1(int o,int l,int r,int p,int row,bool val){
int mid=(l+r)>>1;
if(mid==p){ //中点就是p
conn[mid][row]=val; //修改conn
pushup(seg[o],seg[o<<1],seg[o<<1|1]); //重新pushup
return;
}
if(mid>=p) modify1(lson,p,row,val); //递归修改
else modify1(rson,p,row,val);
pushup(seg[o],seg[o<<1],seg[o<<1|1]); //更新
}
最后还有询问。询问相信大家都会写。
//返回node方便在递归路上合并各个节点的信息
node query(int o,int l,int r,int ql,int qr){
if(l>=ql && r<=qr) return seg[o]; //直接返回
int mid=(l+r)>>1;
if(mid<ql) return query(rson,ql,qr); //不需要左儿子
if(mid>=qr) return query(lson,ql,qr); //不需要有儿子
node ans;
pushup(ans,query(lson,ql,qr),query(rson,ql,qr)); //左右儿子合起来
return ans;
}
然后main里面大力讨论一下是考虑答案u,d,l,r,p还是q。交上去……
WA??????!!!!!!
哦对了,我们来测组数据吧:
4
OPEN 1 1 1 2
OPEN 1 3 1 4
OPEN 1 1 2 1
OPEN 2 1 2 2
OPEN 2 2 2 3
OPEN 2 3 2 4
OPEN 1 4 2 4
ASK 1 2 1 3
EXIT
答案明显是Y。但是我们输出了N?为什么?
我们把这个图画出来:
原来如此。那么求出三个区间大力讨论一下。写法跟pushup差不多,如果理解了pushup那么这一块也应该不难理解。
直接放代码吧:
#include<bits/stdc++.h>
using namespace std;
const int maxn=444444;
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
char ch=getchar();int x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
struct node{
int lft,rig;
bool l,r,u,d,p,q;
}seg[maxn]; //节点
int n;
bool conn[maxn][2];
void pushup(node &x,node l,node r){
x.lft=l.lft;
x.rig=r.rig;
x.l=l.l|(l.u&conn[l.rig][0]&r.l&conn[l.rig][1]&l.d);
x.r=r.r|(r.u&conn[l.rig][0]&l.r&conn[l.rig][1]&r.d);
x.u=(l.u&conn[l.rig][0]&r.u)|(l.p&conn[l.rig][1]&r.q);
x.d=(l.d&conn[l.rig][1]&r.d)|(l.q&conn[l.rig][0]&r.p);
x.p=(l.u&conn[l.rig][0]&r.p)|(l.p&conn[l.rig][1]&r.d);
x.q=(l.d&conn[l.rig][1]&r.q)|(l.q&conn[l.rig][0]&r.u);
}
void build(int o,int l,int r){ //建树
if(l==r){
seg[o].lft=seg[o].rig=l; //叶子节点左右边界
seg[o].u=seg[o].d=1; //左右联通
return;
}
int mid=(l+r)>>1;
build(lson);build(rson);
pushup(seg[o],seg[o<<1],seg[o<<1|1]);
}
void modify1(int o,int l,int r,int p,int row,bool val){ //同行修改
int mid=(l+r)>>1;
if(mid==p){
conn[mid][row]=val;
pushup(seg[o],seg[o<<1],seg[o<<1|1]);
return;
}
if(mid>=p) modify1(lson,p,row,val);
else modify1(rson,p,row,val);
pushup(seg[o],seg[o<<1],seg[o<<1|1]);
}
void modify2(int o,int l,int r,int p,bool val){ //不同行修改,第p列
if(l==r){
seg[o].l=seg[o].r=seg[o].p=seg[o].q=val;
return;
}
int mid=(l+r)>>1;
if(mid>=p) modify2(lson,p,val);
else modify2(rson,p,val);
pushup(seg[o],seg[o<<1],seg[o<<1|1]);
}
node query(int o,int l,int r,int ql,int qr){ //查询区间[ql,qr]的节点
if(l>=ql && r<=qr) return seg[o];
int mid=(l+r)>>1;
if(mid<ql) return query(rson,ql,qr);
if(mid>=qr) return query(lson,ql,qr);
node ans;
pushup(ans,query(lson,ql,qr),query(rson,ql,qr));
return ans;
}
int main(){
n=read();char op[10];
build(1,1,n);
while(~scanf("%s",op) && op[0]!='E'){
int a=read(),b=read(),c=read(),d=read();
switch(op[0]){
case 'C': //关路
if(a==c) modify1(1,1,n,min(b,d),a-1,0); //同一行
else modify2(1,1,n,b,0); //不同行
break;
case 'O': //开路
if(a==c) modify1(1,1,n,min(b,d),a-1,1);
else modify2(1,1,n,b,1);
break;
case 'A': //询问
if(b>d) swap(a,c),swap(b,d); //列编号小的放前面
node ans1=query(1,1,n,b,d),ans2=query(1,1,n,1,b),ans3=query(1,1,n,d,n); //三段
bool flag=false;
if(a==1){
if(c==1){ //左上,右上
if(ans1.u) flag=true; //直接走上面
if(ans2.r&ans1.d&ans3.l) flag=true; //绕路走
}
else{ //左上,右下
if(ans1.p) flag=true; //直接走
if(ans2.r&ans1.d) flag=true; //从左边绕
if(ans3.l&ans1.u) flag=true; //从右边绕
}
}
else{
if(c==1){ //左下,右上
if(ans1.q) flag=true; //直接走
if(ans2.r&ans1.u) flag=true; //从左边绕
if(ans3.l&ans1.d) flag=true; //从右边绕
}
else{ //左下,右下
if(ans1.d) flag=true; //直接走
if(ans2.r&ans1.u&ans3.l) flag=true; //绕路走
}
}
puts(flag?"Y":"N");
}
}
}
这可能是我写过最认真的一篇题解了。
主要还是因为这题太有意义了,而且洛谷上题解没几个图,有点难懂……
希望能帮到大家。两开花,谢谢支持。