考试的时候已经处理出了欧拉序,但并没有关注到它的种种性质
这里给出欧拉序的定义与运用
树的欧拉序是对树进行DFS的一种序列。 有两种形式: 1、在每个结点进和出都加进序列。 2、只要到达每一个结点就把他加进序列。
第一种方法得到的序列和对应的进出状态分别是:
1 2 3 3 4 4 5 5 2 6 7 7 8 8 6 1
进 进 进 出 进 出 进 出 出 进 进 出 进 出 出 出
(每个结点恰好出现了两次)
用这个序列可以解决树上求和的问题:
1、求某个点到根节点的额权值和。方法是:需要在进的点处做加法,出的点处做减法,查询某点就只需要查询对应的前缀即可。
*2、求某个子树的权值和。方法是:需要在进的点处做加法,求某个点最后一次出现的位置的前缀和减去第一次出现的位置的前一个位置的前缀和即可。
第二种方法得到的序列是:
1 2 3 2 4 2 5 2 1 6 7 6 8 6 1
用这一个序列,可以解决的一个问题是:
1、求某两点的LCA。显然这两点之间的区间中,深度最小点就是LCA。这可以用RMQ解决。
2、求某个子树的权值和,方法是:只记录第一次出现的数的值,同样的查询某点就只需要查询该点在欧拉序中最后出现的位置的前缀即可减去第一次出现的额位置-1的前缀和即可。
3、换根操作:这种欧拉序相当于以根为起点围着树跑了一圈,那么我们就可以把欧拉序写成一个环就是:
1 2 3 2 4 2 5 2 1 6 7 6 8 6 1 2 3 2
4 2 5 2 1 6 7 6 8 6以某个点为跟的欧拉序就是以某个点在上面的欧拉序中第一次出现的位置为起点向前走(2*n-1)步,例如以4为根的欧拉序就是
1 2 3 2 4 2 5 2 1 6 7 6 8 6 1 2 3 2
4 2 5 2 1 6 7 6 8 6
L————————————————-R//以4为根的欧拉序,同时可以维护和之类的东西。
其实这道题并用不到这里的任意性质
我们要做的是用一种顺序使欧拉序最多被遍历一次
那么可以枚举点的位置,首先从起点到根的路径上不会出现重复的点
那么根据现在所在的点去询问点在欧拉序中所在的位置
从一个点出发,所到达的第t个点,就是它欧拉序后的第t个点
还有一个难点是怎么判断能够相遇
只有同时从两个点出发才能满足相遇
那么就记录出发的点的位置,即在欧拉序中第一次出现的位置(可以证明一个点最多遍历子树一遍,不然一定不会相遇)
代码如下:
void dfs(int x,int f) {
Lx[x]=++T;
D[x].pb(T);
fa[x]=f;
SFOR(i,0,edge[x].size()) {
dfs(edge[x][i],x);
D[x].pb(++T);
}
}
void clear() {
FOR(i,1,n)edge[i].clear();
FOR(i,1,n)D[i].clear();
memset(mark,0,sizeof(mark));
T=0;
}
void solve() {
dfs(1,0);
int ans=0,t=0;
for(int i=sta;i!=0;i=fa[i])mark[Lx[i]]=1;
while(sta!=0){
SFOR(i,0,D[sta].size()){
int y=D[sta][i]-t;
if(y<=0||!mark[y])continue;
mark[y]=0;
ans++;
}
sta=fa[sta];
t++;
}
printf("%d\n",ans);
}