树
树
启示:
- 无输出不一定是死循环,也可能是函数类型错误(void-->int)
- LCA中两个节点一起往上跳的时候,要循环到0。
- LCA里面fa[x][i]啊
tarjan求LCA
走过且走过其子树的点为2,走过但未走过其子树的点为1,为走过的点为0。
走到一个的y,若x是2,则x的第一个为1的祖先为lca。
欧拉序
时间戳:按照dfs遍历,节点被访问的顺序
dfs序:
欧拉:dfs经过的节点顺序。
求LCA:找欧拉序[dfn[u],dfn[v]]中深度最小的节点。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int dfn[N],ol[N],d[N],a[N];
int cnt=0;
int st[N][22];
struct node{
int v,nxt;
}e[N];
int h[N],tot;
int n,m,s;
void add(int x,int y){
e[++tot].nxt=h[x];
e[tot].v=y;
h[x]=tot;
}
void dfs(int u,int u_fa){
dfn[u]=++cnt;
a[cnt]=u;
d[u]=d[u_fa]+1;
for(int i=h[u];~i;i=e[i].nxt){
int v=e[i].v;
if(v==u_fa) continue;
dfs(v,u);
a[++cnt]=u;
}
}
void sttt(){
for(int i=1;i<=cnt;i++) st[i][0] = a[i];
int h=log(cnt)/log(2)+1;
for(int i = 1;i <= h;i++){
for(int j=1;j+(1<<i)-1 <= cnt;j++){
int u,v;
u=st[j][i-1],v=st[j+(1<<(i-1))][i-1];
st[j][i] = (d[u]<d[v])?u:v;
}
}
}
int lca(int u,int v){
if(dfn[u]>dfn[v]) swap(u,v);
int L=dfn[u],R=dfn[v];
int k=log(R-L+1)/log(2);
int p1=st[L][k],p2=st[R-(1<<k)+1][k];
return d[p1]<d[p2]?p1:p2;
}
int main(){
memset(h,-1,sizeof h);
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(s,0);
sttt();
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
The merchant
题意:
\(n\) 个城市构成一棵树,每个城市一个权值表示买入或卖出的值,查询多条路径(x到y)买入物品再卖出的最大利润(只能买入卖出一次且一个物品),若小于0则输出0。\(n,w_i,q\le5000\)
思路:
分为三种,答案在向上路径,向下路径,向下max-向上min。直接维护四个值,最大值,最小值,向上最大值,向下最大值。维护方式和lca相似。可以直接一起算。
细节上举个例子:对于向上路径,向上的最大值就是\(up[u][i],up[fa[u][i-1]][i],mx[fa[u][i-1]][i-1]-mi[u][i-1]\)。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=5e4+5;
const int INF=0x3f3f3f3f;
int n,m,s;
struct node{
int v,nxt;
}e[N<<1];
int a[N];
int d[N],fa[N][20],mx[N][20],mi[N][20],up[N][20],dn[N][20];
int tot,h[N];
void add(int x,int y){
e[++tot].nxt=h[x];
e[tot].v=y;
h[x]=tot;
}
void dfs(int u,int u_fa){
d[u]=d[u_fa]+1;
for(int i=h[u];~i;i=e[i].nxt){
int v=e[i].v;
if(v==u_fa) continue;
fa[v][0]=u;
mx[v][0]=max(a[v],a[fa[v][0]]);
mi[v][0]=min(a[v],a[fa[v][0]]);
up[v][0]=max(0,a[fa[v][0]]-a[v]);
dn[v][0]=max(0,a[v]-a[fa[v][0]]);
dfs(v,u);
}
}
void fa_fa(){
for(int i=1;i<20;i++)
for(int j=1;j<=n;j++){
fa[j][i]=fa[fa[j][i-1]][i-1];
mi[j][i]=min(mi[j][i-1],mi[fa[j][i-1]][i-1]);
mi[j][i]=max(mi[j][i],0);
mx[j][i]=max(mx[j][i-1],mx[fa[j][i-1]][i-1]);
int mm=fa[j][i-1];
up[j][i]=max(up[j][i-1],max(up[mm][i-1],mx[mm][i-1]-mi[j][i-1]));
up[j][i]=max(up[j][i],0);
dn[j][i]=max(dn[j][i-1],max(dn[mm][i-1],mx[j][i-1]-mi[mm][i-1]));
dn[j][i]=max(dn[j][i],0);
}
}
int lca(int x,int y){//x->y
int mii,mxx,um,dm,fl=0;
mxx=um=dm=0;
mii=INF;
if(d[x]<d[y]) swap(x,y),fl=1;
for(int i=19;i>=0;i--){
if(d[fa[x][i]]>=d[y]) {
//fl==0 x->y
if(!fl){
um=max(max(um,up[x][i]),mx[x][i]-mii);
mii=min(mii,mi[x][i]);
}else{
dm=max(max(dm,dn[x][i]),mxx-mi[x][i]);
mxx=max(mxx,mx[x][i]);
}
x=fa[x][i];
}
}
if(x==y) {
return max(um,max(dm,mxx-mii));
}
for(int i=19;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
if(!fl){
um=max(max(um,up[x][i]),mx[x][i]-mii);
mii=min(mii,mi[x][i]);
dm=max(max(dm,dn[y][i]),mxx-mi[y][i]);
mxx=max(mxx,mx[y][i]);
}else{
um=max(max(um,up[y][i]),mx[y][i]-mii);
mii=min(mii,mi[y][i]);
dm=max(max(dm,dn[x][i]),mxx-mi[x][i]);
mxx=max(mxx,mx[x][i]);
}
x=fa[x][i],y=fa[y][i];
}
}
int i=0;
if(!fl){
um=max(max(um,up[x][i]),mx[x][i]-mii);
mii=min(mii,mi[x][i]);
dm=max(max(dm,dn[y][i]),mxx-mi[y][i]);
mxx=max(mxx,mx[y][i]);
}else{
um=max(max(um,up[y][i]),mx[y][i]-mii);
mii=min(mii,mi[y][i]);
dm=max(max(dm,dn[x][i]),mxx-mi[x][i]);
mxx=max(mxx,mx[x][i]);
}
return max(um,max(dm,mxx-mii));
}
int main(){
memset(mi,0x3f,sizeof mi);
memset(h,-1,sizeof h);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
dfs(1,0);
fa_fa();
scanf("%d",&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
int z=lca(x,y);
printf("%d\n",max(z,0));
}
return 0;
}
注意:
- LCA最后一步虽然只需要求fa[x][0],但对于 \(x\) 和 \(y\) 处的值都需要继续维护。
P2597 [ZJOI2012] 灾难
code:
MYJ_aiie
[HEOI2016/TJOI2016] 树
题意:
一棵树,两种操作。Q:查询某个结点最近的一个打了标记的祖先。C:对某个结点打上标记。(初始只有结点 1 有标记,其他无标记,而且对于某个结点,可以打多次标记。)\(N,Q\leq100000\)
思路:
打标记需要维护的值太多,我们直接逆序操作,删除标记的过程可以看作并查集合并。将标记删为0的节点的最近标记祖先变为上方最近的标记祖先。查询就是找祖先。
code:MYJ_aiie
注意:
- 初始时的值也要处理。
P1600 [NOIP 2016 提高组] 天天爱跑步
题意:
一棵树。有 \(m\) 个玩家,第 \(i\) 个玩家第 \(0\) 时刻出发从 \(s_i\) 走到 \(t_i\) 。树上每个节点有一个观测员,第 \(i\) 个观测员只有在 \(w_i\) 可以观测。

浙公网安备 33010602011771号