题目描述
【恶意玩梗】Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind和孩子们决定在家里玩捉迷藏游戏。他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋子都互相可达。
游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯。在起初的时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia希望知道可能的最远的两个孩子的距离(即最远的两个关灯房间的距离)。
我们将以如下形式定义每一种操作:
C(hange) i
改变第i个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。
G(ame)
开始一次游戏,查询最远的两个关灯房间的距离。
输入
第一行包含一个整数 N ,表示房间的个数,房间将被编号为
1,2,3…N 的整数。接下来 N−1 行每行两个整数 a ,b ,表示房间 a 与房间b 之间有一条走廊相连。接下来一行包含一个整数 Q ,表示操作次数。接着Q 行,每行一个操作,如上文所示。
输出
对于每一个操作Game,输出一个非负整数,表示最远的两个关灯房间的距离。若只有一个房间是关着灯的,输出0;若所有房间的灯都开着,输出-1。
样例输入
8
1 2
2 3
3 4
3 5
3 6
6 7
6 8
7
G
C 1
G
C 2
G
C 1
G
样例输出
4
3
3
4
网上其他题解主要分两种,一种是点分治【这里没有】,另一种就是括号序列+线段树。其实还看到了一个树链剖分的。
这题的括号序列做法先出于CQX冬令营讲稿《数据结构的提炼与压缩》,详见岛娘博客:某岛,这里做一下解释。
用括号表示,[其实就相当于沿着边向上走一步,]其实就相当于沿着边向下走一步。[]就是这个点入了一次又出了一次。
对于定义的
S(a,b)
,以及
dis(S),right_plus,right_minus,left_plus,left_minus
的转移
dis(S)=max{dis(S1),left_minus(S2)+right_plus(S1),left_plus(S2)+right_minus(S1),dis(S2)}
dis(S1) , dis(S2) 不用多说,中间那两个则是跨过连接点去掉匹配的括号后新的括号序列的 dis ,取max即可得到这个合并了 S1 、 S2 后所得到 dis(S) 的最优值。
right_plus(S)right_minus(S)left_plus(S)left_minus(S)=max{right_plus(S1)−c+d,right_minus(S1)+c+d,right_plus(S2)}=max{right_minus(S1)+c−d,right_minus(S2)}=max{left_plus(S2)−b+a,left_minus(S2)+b+a,left_plus(S1)}=max{left_minus(S2)+b−a,left_minus(S1)}
对于
right_plus
,分别为
b>c
,
b<c
,和只要
S2
的情况。
而对于
right_minus
,分别为
c>b
,和只要
S2
的情况。
对于
left
同理。【自己太笨了当初看这里看了好长时间】
细节处理和岛娘相同即可。
个人认为这样的思路算不上线段树吧大概,写的是归并貌似好一点。但是这个还是搞得像非递归线段树一样。
上代码
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
#define lch tree[a*2+1]
#define rch tree[a*2+2]
const int MIN=-(1<<25);
vector<int> graph[100000];
//房间状态,0为灭,1为亮
bool state[100000]={0};
//n:灭灯房间数 t:临时变量
//pos:房间对应树中位置
//tree第二维:a, b, left_plus, right_plus, left_minus, right_minus, dis
int n, t, pos[100000]={0}, tree[1<<21][7]={0};
//叶子结点赋初值
void v(int a, int p){
tree[a][0]=tree[a][1]=0;
tree[a][2]=tree[a][3]=tree[a][4]=tree[a][5]=tree[a][6]=MIN;
//这里的赋值是这样的,对于每个房间,共三个叶子节点,相当于[A]
//-1就是[,-2就是],房间节点则a=b=0
//这里也看了好半天
if(p==-1)
tree[a][1]=1;
else if(p==-2)
tree[a][0]=1;
else if(!state[p])
tree[a][2]=tree[a][3]=tree[a][4]=tree[a][5]=0;
}
//深搜建图
void dfs(int a, int fa){
v(t++, -1);
v(pos[a]=t++, a);
for(vector<int>::iterator it=graph[a].begin();it!=graph[a].end();it++)
if(*it!=fa)
dfs(*it, a);
v(t++, -2);
}
//合并两个子节点,套用公式
int merge(int a){
if(lch[1]<rch[0]){
tree[a][0]=lch[0]-lch[1]+rch[0];
tree[a][1]=rch[1];
}
else{
tree[a][0]=lch[0];
tree[a][1]=lch[1]-rch[0]+rch[1];
}
tree[a][2]=max(lch[2], max(rch[2]-lch[1]+lch[0], rch[4]+lch[1]+lch[0]));
tree[a][3]=max(lch[3]-rch[0]+rch[1], max(lch[5]+rch[0]+rch[1], rch[3]));
tree[a][4]=max(rch[4]+lch[1]-lch[0], lch[4]);
tree[a][5]=max(rch[5], lch[5]+rch[0]-rch[1]);
tree[a][6]=max(lch[6], max(rch[4]+lch[3], max(rch[2]+lch[5], rch[6])));
}
//自底向上修改
void M(int a){
while(a!=0){
merge(a);
a=(a-1)/2;
}
merge(0);
}
int main(){
int p=1;
scanf("%d", &n);
//子节点至少从3n开始,同时保证上方(整个图)是一棵完全二叉树
while(p<3*n)
p<<=1;
t=p-1;
int a, b;
for(int i=0;i<n-1;i++){
scanf("%d%d", &a, &b);
a--; b--;
graph[a].push_back(b);
graph[b].push_back(a);
}
dfs(0, -1);
//合并节点建树
for(int i=p-2;i>=0;i--)
merge(i);
int q;
char c;
scanf("%d", &q);
for(int i=0;i<q;i++){
getchar();
c=getchar();
//Change
if(c=='C'){
scanf("%d", &a);
a--;
state[a]=!state[a];
if(state[a]) n--;
else n++;
//修改树
v(pos[a], a);
M((pos[a]-1)/2);
}
//Game
else{
if(!n) printf("-1\n");
else if(n==1) printf("0\n");
else printf("%d\n", tree[0][6]);
}
}
return 0;
}
还是得用好归并思想。点分治什么的以后再说。