您的位置:首页 > 其它

[BZOJ1095]Hide 捉迷藏--括号序列&&线段树

2017-09-27 22:11 357 查看

岛姐 orz

看题解看了接近两个小时才想通这是在干什么

经常看着某一部分就忘了上面一部分要干什么了,然后一脸蒙…

Emmmmm好在最后还是理解到了,十分巧妙

有点晚了,题解有时间再写吧,先贴几个挺不错的题解,一起看可能比较容易理解

—>岛姐的题解

–>博客园-沐阳

UPD at 2018.1.10 me的动态点分治做法传送门

自带大常数的代码

/**************************************************************
Problem: 1095
User: Izumihanako
Language: C++
Result: Accepted
Time:2212 ms
Memory:28280 kb
****************************************************************/

/*-----------------------------------------
岛姐orz
这种解法比较神奇
把树上的信息转换成了序列信息
然后对序列进行花式维护

整个题的重点在于线段树的updata函数
通过多维护几个东西,来快速的计算出答案
-----------------------------------------*/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , Q , head[100005] , tp ;
int pos[100005] , cate[300005] , tot , dark[100005] ;
struct Path{
int pre , to ;
}p[200005] ;
struct Node{
int dis , RP , RM , LP , LM , a , b ;
//RP = right_plus , RM = right_minus
//LP = left_plus , LM = left_minus
Node *ls , *rs ;
void set( int x ){
/*a代表的是']' 开口向右的那种括号
b代表的是'[' 开口向左的那种括号
因为把可以匹配的括号消除之后,剩下的一定是这样的:
.....]]]]]][[[[[.....
(a,b)二元组即可表示这个括号
*/

/*如果为白点或者括号,就全部赋值为负无限大
这样保证了,凡是有括号是跨过了白点的,通通都取不到
*/
a = ( x == -2 ) ;
b = ( x == -1 ) ;
dis = -0x3f3f3f3f ;
if( x >= 1 && dark[x] ){
//如果没有关灯,就设为0,相对于负无穷,这就代表开始了
RP = RM = LP = LM = 0 ;
} else RP = RM = LP = LM = -0x3f3f3f3f ;
}

void updata(){
if( rs->a > ls->b ){
a = ls->a + rs->a - ls->b ; b = rs->b ;
} else {
a = ls->a ; b = ls->b - rs->a + rs->b ;
}
//因为是左右拼接,所以是ls->Rx + rs->Lx才能拼接起中间部分
dis = max( ls->dis , rs->dis ) ;
dis = max( dis , ls->RM + rs->LP ) ;
dis = max( dis , ls->RP + rs->LM ) ;
//因为RP必须与右端联通,所以需要直接 - rs->a + rs->b ,下面同理
RP = max( ls->RP - rs->a + rs->b , ls->RM + rs->a + rs->b ) ;
RP = max( RP , rs->RP ) ;
RM = max( ls->RM + rs->a - rs->b , rs->RM ) ;
//LM维护的是b-a,因此和RM不太一样,是反过来的
LP = max( rs->LP + ls->a - ls->b , rs->LM + ls->a + ls->b ) ;
LP = max( LP , ls->LP ) ;
LM = max( rs->LM + ls->b - ls->a , ls->LM ) ;
}

}w[600005] , *root , *tw = w ;

void In( int t1 , int t2 ){
p[++tp].pre = head[t1] ;
p[ head[t1] = tp ].to = t2 ;
}

void dfs( int u , int f ){
/*进入的时候为'['
在这里把点也直接放进序列,更方便处理
(就可以很方便的把白点设置成-inf从而阻断跨过白点的序列)
*/
cate[++tot] = -1 ;
pos[ cate[++tot] = u ] = tot ;
for( int i = head[u] ; i ; i = p[i].pre ){
int v = p[i].to ;
if( v != f ) dfs( v , u ) ;
}
//出去的时候为']'
cate[++tot] = -2 ;
}

Node *build( int lf , int rg ){
//线段树的日常--build
Node *nd = ++tw ;
if( lf == rg )
nd->set( cate[lf] ) ;
else {
int mid = ( lf + rg ) >> 1 ;
nd->ls = build( lf , mid ) ;
nd->rs = build( mid+1 , rg ) ;
nd->updata() ;
} return nd ;
}

void Modify( Node *nd , int lf , int rg , int pos ){
//线段树的日常--modify
if( lf == rg ){
nd->set( cate[lf] ) ;
return ;
}
int mid = ( lf + rg ) >> 1 ;
if( pos <= mid ) Modify( nd->ls , lf , mid , pos ) ;
else             Modify( nd->rs , mid+1 , rg , pos ) ;
nd->updata() ;
}

void solve(){
char ss[5] ;
scanf( "%d" , &Q ) ;
for( int i = 1 , x ; i <= Q ; i ++ ){
scanf( "%s" , ss ) ;
switch( ss[0] ){
case 'G':{
//答案会在不断地updata后被统计到根节点
//因此每次询问直接输出根节点的dis即可
printf( "%d\n" , root->dis ) ;
break;
}
case 'C':{
scanf( "%d" , &x ) ;
//注意这里需要把开关灯状态更新
dark[x] ^= 1 ;
Modify( root , 1 , tot , pos[x] ) ;
break;
}
}
}
}

//读入优化 等于scanf
int read_(){
int rt = 0;
char ch = getchar() ;
while( ch < '0' || ch > '9' ) ch = getchar() ;
while( ch >='0' && ch <='9' ) rt = (rt<<3) + (rt<<1) + ch - '0' , ch = getchar() ;
return rt;
}

int main(){
scanf( "%d" , &N ) ;
//一开始灯泡全部处于关闭状态,注意需要将标记打好
for( register int i = 1 ; i <= N ; i ++ ) dark[i] = 1 ;
//日常建边&&一系列预备操作
for( register int i = 1 , t1 , t2 ; i < N ; i ++ ){
t1 = read_() ; t2 = read_();
In( t1 , t2 ) ; In( t2 , t1 ) ;
}
dfs( 1 , 1 ) ;
root = build( 1 , tot ) ;
solve() ;
}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: