P3979 遥远的国度
长者的题qwq
在没有换根之前,都是可以裸的树剖维护的
一个最普通的询问
如果我们换一次根
就会变成一下情况
然而我们所查询的子树的结构并没有发生改变,为什么呢?
我们可以观察到,所查询的根并没有在当前的根到原来跟的路径上。
可以想象成你将所换的根提起来,在提的时候,所有结构发生改变的子树的根,都是所换根到原来根的路径上的节点,因为他们的子树中的节点要成为他们的父节点(or级别更高,不知道高到哪里去了)
就像上面一样
查询时就是查询红线以外的的节点
]
看起来很简单的样子qwq,不就是如果查询节点的\(A\)和当前根节点的\(R\)的\(Lca\)是\(A\)的时候,就查询\(A\)的祖先and其祖先和\(A\)除了\(R\)所在的
子树,以外的所有节点么。
不过怎么查呢?
不要忘了这事一道树剖题,可以利用\(dfs\)序来进行
一颗子树中的\(dfs\)序是连续的
若一个节点\(A\)在一个以\(R\)为根的子树中
其\(dfs\)序大于\(R\)的\(dfs\)序,小于\(R\)的\(dfs\)+\(R\)的子树大小。
所以我们可以枚举\(A\)的儿子,再根据上面这一条定理判断
然后如何使用树剖维护呢
还是根据一棵树中的\(dfs\)序是连续的来做。
如果我们将\(R\)所在的子树的链在线段树中都删去,那么剩下的链就是我们需要查询的区间。 所以我们只需要查询两次就可以了
#include<cstdio>
#include<algorithm>
#include<iostream>
using std::swap;
using std::min;
const int maxn=101000;
int n,m,r,now_r;
struct node
{
int p;
int nxt;
};
node line[maxn<<1];
int head[maxn],tail;
void add(int a,int b)
{
line[++tail].p=b;
line[tail].nxt=head[a];
head[a]=tail;
}
int dep[maxn],id[maxn],f[maxn],top[maxn];
int son[maxn],size[maxn],num;
int val[maxn],base[maxn];
int t[maxn<<2],tag[maxn<<2];
void build(int root,int l,int r)
{
if(l==r)
{
t[root]=base[l];
return ;
}
int mid=(l+r)>>1;
build(root<<1,l,mid);
build(root<<1|1,mid+1,r);
t[root]=min(t[root<<1],t[root<<1|1]);
}
void push_down(int root,int l,int mid,int r)
{
if(!tag[root]) return ;
int ls=root<<1,rs=root<<1|1;
tag[ls]=tag[root];
tag[rs]=tag[root];
t[ls]=tag[root];
t[rs]=tag[root];
tag[root]=0;
return ;
}
void updata(int root,int l,int r,int al,int ar,int val)
{
if(l>ar||r<al) return ;
if(l>=al&&r<=ar)
{
t[root]=val;
tag[root]=val;
return ;
}
int mid=(l+r)>>1;
push_down(root,l,mid,r);
updata(root<<1,l,mid,al,ar,val);
updata(root<<1|1,mid+1,r,al,ar,val);
t[root]=min(t[root<<1],t[root<<1|1]);
}
int query(int root,int l,int r,int al,int ar)
{
if(l>ar||r<al) return 0x7fffffff;
if(l>=al&&r<=ar) return t[root];
int mid=(l+r)>>1;
push_down(root,l,mid,r);
return min(query(root<<1,l,mid,al,ar),
query(root<<1|1,mid+1,r,al,ar));
}
/*----------------------------------------------------线段树*/
void dfs1(int now,int fa,int d)
{
dep[now]=d;
size[now]=1;
f[now]=fa;
int W=-1;
for(int i=head[now];i;i=line[i].nxt)
if(line[i].p!=fa)
{
dfs1(line[i].p,now,d+1);
size[now]+=size[line[i].p];
if(size[line[i].p]>W)
son[now]=line[i].p,W=size[line[i].p];
}
}
void dfs2(int now,int tf)
{
top[now]=tf;
id[now]=++num;
base[num]=val[now];
if(!son[now]) return ;
dfs2(son[now],tf);
for(int i=head[now];i;i=line[i].nxt)
if(line[i].p!=f[now]&&line[i].p!=son[now])
dfs2(line[i].p,line[i].p);
}
void seg_updata(int a,int b,int c)
{
while(top[a]!=top[b])
{
if(dep[top[a]]<dep[top[b]]) swap(a,b);
updata(1,1,num,id[top[a]],id[a],c);
a=f[top[a]];
}
if(dep[a]>dep[b]) swap(a,b);
updata(1,1,num,id[a],id[b],c);
return ;
}
int lca(int a,int b)
{
while(top[a]!=top[b])
{
if(dep[top[a]]<dep[top[b]]) swap(a,b);
a=f[top[a]];
}
return dep[a] < dep [b] ? a : b ;
}
int tree_query(int a)
{
if(a==now_r) //如果是当前根,直接输出就可以了
return query(1,1,num,1,num);
int Lca=lca(now_r,a);//树剖求lca
if(Lca!=a)//如果是第一种情况
return query(1,1,num,id[a],id[a]+size[a]-1);//直接模板一顿套
int S;
for(int i=head[a];i;i=line[i].nxt)
if(id[line[i].p]<=id[now_r]&&id[line[i].p]+size[line[i].p]-1>=id[now_r]&&line[i].p!=f[a])
{
S=line[i].p;
break;
}//使用规律进行查找,这里可以使用倍增加速
return min(query(1,1,num,1,id[S]-1),query(1,1,num,id[S]+size[S],num));//分成两段进行查询
}
int main()
{
scanf("%d%d",&n,&m);
int a,b,c;
for(int i=1;i<n;i++)
{
scanf("%d%d",&a,&b);
add(a,b);add(b,a);
}
for(int i=1;i<=n;i++)
scanf("%d",&val[i]);
scanf("%d",&r);
now_r=r;
dfs1(r,0,1);
dfs2(r,r);
build(1,1,num);
for(int i=1;i<=m;i++)
{/*
scanf("%d%d",&a,&b);
printf("%d\n",lca(a,b));*/
scanf("%d",&a);
if(a==1)
{
scanf("%d",&b);
now_r=b;
continue;
}
if(a==2)
{
scanf("%d%d%d",&a,&b,&c);
seg_updata(a,b,c);
continue;
}
if(a==3)
{
scanf("%d",&b);
printf("%d\n",tree_query(b));
continue;
}
}
}