我记得我学过虚树啊 除了做过的题目有提交的痕迹 脑子空空如也。
今天一定要复习好虚树 我没剩多少时间了。
1.虚树是干嘛的?
对于一道题目 我们发现其每次询问树上的一些点集的某种定义下的答案 通常我们不需要再次遍历整棵树来寻找答案 可以利用题目中给出的这些点集建立一颗不存在的树 即称虚树。
2.如何构建
我们可以发现 如果单纯的把点插到一颗不存在的树上很好插入 但是我们不一定能维护好类似于原本树的形态。所以这个时候我们需要借助两两点之间的LCA来帮忙构建这棵树 维护好这棵树的基本形态。
3.复杂度的保证:由于是询问中的点集 我们可以将其按原本的dfs序排序 然后按顺序插入到我们的虚树当中去 并两两点求一波LCA
可以发现 LCA可以O(1)也可以logn 由于时间复杂度的瓶颈在于排序 所以总复杂度为点集大小K*logn.
4.应用
大多数题目都是让我们构建出虚树然后在虚树上搞点事情 如树形dp 点分治 线段树优化建图什么的。
总之这个技巧还是挺重要的。
虚树构建的过程:
虚树的构建依赖于单调栈,我们利用单调栈来维护 根到某个节点的一条笔直的链 这样我们就可以把整个基础的树的形态给完整的演绎出来。
想着 单调栈维护的是什么东西+画图思考 就可以得到一个虚树的构建过程了 一般这样的话就不容易写错了。
为什么要在弹栈的时候建边? 由于有些点深度小但是是后来以LCA的形式加入的 如果在某个点一开始就加入那么显然后面来一个LCA的话建边会错乱。
由于两两点之间的LCA只有一个 所以我们构建的虚树的空间复杂度也是询问点点集的复杂度。
code:
inline void insert(int x)
{
if(s[top]==1){s[++top]=x;return;}
int lca=LCA(s[top],x);
if(s[top]==lca){s[++top]=x;return;}
while(top>1&&dfn[s[top-1]]>=dfn[lca])
{
ADD(s[top-1],s[top]);
--top;
}
if(s[top]!=lca)
{
ADD(lca,s[top]);
s[top]=lca;
}
s[++top]=x;
}
int main()
{
sort(w+1,w+1+n,cmp);
s[top=1]=1;
rep(1,n,i)if(w[i]!=1)insert(w[i]);
while(top>1)ADD(s[top-1],s[top]),--top;
}
值得注意的是建的边可以是单向的 如果双向边没啥大用的话...
LINK:luogu2495SDOI2011消耗战
这道题目 初看是一道比较裸的树形dp 但是每次给出了关键点了 所以我们直接构建虚树在虚树上dp即可。
值得一提的是我们很容易发现 两个点同时为关键点且拥有祖先关系 那么 其中的一个点可以不要 因为祖先是一定要被割断的 割断的同时儿子也一定被隔断了...
所以我们在构建虚树的时候 可以把s[top]==lca时 不把x加入栈中 因为s[top]此时一定是关键点 且s[top]还是x的祖先。
估计是边权范围假了 INF 1e9 wa了
const int MAXN=250010;
int n,m,k,len,T,cnt,top;
int fa[MAXN][20],d[MAXN],dfn[MAXN],s[MAXN],Log[MAXN],a[MAXN];
int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1];
ll f[MAXN],mx[MAXN],e[MAXN<<1];vector<int>g[MAXN];
inline int cmp(int a,int b){return dfn[a]<dfn[b];}
inline void add(int x,int y,int z)
{
ver[++len]=y;
nex[len]=lin[x];
lin[x]=len;
e[len]=z;
}
inline void dfs(int x,int father)
{
d[x]=d[father]+1;
fa[x][0]=father;dfn[x]=++cnt;
rep(1,Log[d[x]],i)fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father)continue;
mx[tn]=min(mx[x],e[i]);
dfs(tn,x);
}
}
inline void add(int x,int y){g[x].push_back(y);}
inline int LCA(int x,int y)
{
if(d[x]<d[y])swap(x,y);
for(int i=Log[d[x]];i>=0;--i)
if(d[fa[x][i]]>=d[y])x=fa[x][i];
if(x==y)return x;
for(int i=Log[d[x]];i>=0;--i)
if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
inline void insert(int x)
{
if(top==1){s[++top]=x;return;}
int lca=LCA(s[top],x);
if(lca==s[top])return;
while(top>1&&dfn[s[top-1]]>=dfn[lca])
{
add(s[top-1],s[top]);
--top;
}
if(s[top]!=lca)
{
add(lca,s[top]);
s[top]=lca;
}
s[++top]=x;
}
inline void dp(int x)
{
f[x]=mx[x];
if(!g[x].size())return;
ll sum=0;
for(unsigned int i=0;i<g[x].size();++i)
{
int tn=g[x][i];
dp(tn);
sum+=f[tn];
}
f[x]=min(f[x],sum);
g[x].clear();
}
int main()
{
freopen("1.in","r",stdin);
get(n);
rep(2,n,i)
{
int x,y,z;
get(x);get(y);get(z);
add(x,y,z);add(y,x,z);
Log[i]=Log[i>>1]+1;
}
mx[1]=INF;dfs(1,0);
get(m);
rep(1,m,i)
{
int k;get(k);s[top=1]=1;
rep(1,k,j)get(a[j]);
sort(a+1,a+1+k,cmp);
rep(1,k,j)insert(a[j]);
while(top>1)add(s[top-1],s[top]),--top;
dp(1);printf("%lld
",f[1]);
}
return 0;
}
这道虚树的模板题写起来挺舒服的。。明天一定要把世界树给肝出来!
一棵树 给出m个关键点 某个点被最近的关键点管辖 距离一样编号最小的优先。求每个关键点管辖的点的个数。
所有点都是关键点,那当然每个点被自己管辖了...考虑怎么做 我们可以进行暴力的树形dp 但是很麻烦 直接bfs即可。。
考虑优化这个过程 毕竟是存在优化的空间的 我们建立出来虚树。
可以发现如果我们面对两个点都是关键点 我们可以求出一个mid来割分 但是可能不存在这种情况 但是可以逐边进行考虑。
每一条边都有分属关系 可能被割开 也可能全部归属于某个点 而每条边都有归属 我们可以先dp一下求出归属关系。
一条边的两个端点的归属关系可能不同 这个时候我们进行割分 相同的话显然直接决定归属。
割分 我们直接算出mid 倍增可以统计虚边上的贡献 考虑一下除了虚边上的点 还有自己本身连接的点 这些点 对于一个点其实是其子树大小-自己下方的关键点所属链的第一个儿子 也可以倍增求。至此可以发现问题就解决了 关键是发现我们可以逐边考虑。
存在坑点 就是找mid的时候 要用原来整条链的深度拿出来 然后除以2特判一下 倍增向上跳即可。
这道题的关键还是要想到建出虚树之后逐边考虑计算贡献。。
//#include<bitsstdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define INF 100000000000000ll
#define ld long double
#define pb push_back
#define put(x) printf("%d
",x)
#define rep(p,n,i) for(RE int i=p;i<=n;++i)
#define pii pair<ll,ll>
#define F first
#define mk make_pair
#define mod 64123
#define RE register
#define get(x) x=read()
#define gt(x) scanf("%d",&x)
#define go(x) for(int i=lin[x],tn=ver[i];i;tn=ver[i=nex[i]])
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
RE int x=0,f=1;char ch=getc();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
return x*f;
}
const int MAXN=300010;
int n,cnt,m,len,top;
int a[MAXN],dfn[MAXN],s[MAXN],b[MAXN],vis[MAXN],w[MAXN];
int Log[MAXN],f[MAXN][20],d[MAXN],sz[MAXN],c[MAXN];
int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1],ans[MAXN];
inline int cmp(int x,int y){return dfn[x]<dfn[y];}
inline void add(int x,int y)
{
ver[++len]=y;
nex[len]=lin[x];
lin[x]=len;
}
inline void dfs(int x,int father)
{
f[x][0]=father;d[x]=d[father]+1;dfn[x]=++cnt;
for(int i=1;i<=Log[d[x]];++i)f[x][i]=f[f[x][i-1]][i-1];
sz[x]=1;go(x)if(tn!=father)dfs(tn,x),sz[x]+=sz[tn];
}
inline int LCA(int x,int y)
{
if(d[x]<d[y])swap(x,y);
for(int i=Log[d[x]];i>=0;--i)
if(d[f[x][i]]>=d[y])x=f[x][i];
if(x==y)return x;
for(int i=Log[d[x]];i>=0;--i)
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
inline void insert(int x)
{
if(top<=1){s[++top]=x;return;}
int lca=LCA(s[top],x);
while(top>1&&dfn[s[top-1]]>=dfn[lca])
{
add(s[top-1],s[top]);
--top;
}
if(s[top]!=lca)add(lca,s[top]),s[top]=lca;
s[++top]=x;
}
inline int dis(int x,int y){return d[x]+d[y]-d[LCA(x,y)]*2;}
inline void dfs1(int x)
{
b[x]=vis[x]?x:0;
go(x)
{
dfs1(tn);
if(!b[x])b[x]=b[tn];
else
{
int dis1=dis(b[x],x);
int dis2=dis(b[tn],x);
if(dis2<dis1||dis2==dis1&&b[tn]<b[x])b[x]=b[tn];
}
}
}
inline void dfs2(int x)
{
go(x)
{
int dis1=dis(b[x],tn);
int dis2=dis(b[tn],tn);
if(dis1<dis2||dis2==dis1&&b[x]<b[tn])b[tn]=b[x];
dfs2(tn);
}
}
inline void dp(int x)
{
w[x]=sz[x];
go(x)//处理每一条虚边
{
dp(tn);
int v=tn,mid=tn;//分别为中点和向上的第一个点
for(int j=Log[d[tn]];j>=0;--j)if(d[f[v][j]]>d[x])v=f[v][j];
w[x]-=sz[v];
if(b[x]==b[tn]){ans[b[x]]+=sz[v]-sz[tn];continue;}
//求出分界点
if(v==tn)continue;
int midd=(d[tn]-d[x])+dis(b[tn],tn)+dis(b[x],x);
if(midd&1)midd=d[b[tn]]-(midd>>1);
else midd=d[b[tn]]-(midd>>1)+(b[x]<b[tn]);
for(int j=Log[d[tn]];j>=0;--j)if(d[f[mid][j]]>=midd)mid=f[mid][j];
ans[b[x]]+=sz[v]-sz[mid];
ans[b[tn]]+=sz[mid]-sz[tn];
}
ans[b[x]]+=w[x];lin[x]=0;
}
int main()
{
freopen("1.in","r",stdin);
get(n);
rep(2,n,i)
{
int x,y;
get(x);get(y);
add(x,y);add(y,x);
Log[i]=Log[i>>1]+1;
}
dfs(1,0);get(m);
memset(lin,0,sizeof(lin));
rep(1,m,i)
{
int x;get(x);s[top=1]=1;len=0;
rep(1,x,j)c[j]=get(a[j]),vis[a[j]]=1;
sort(a+1,a+1+x,cmp);
rep(1,x,j)if(a[j]!=1)insert(a[j]);
while(top>1)add(s[top-1],s[top]),--top;
dfs1(1);dfs2(1);dp(1);
rep(1,x,j)printf("%d ",ans[c[j]]),ans[c[j]]=vis[c[j]]=0;
puts("");
}
return 0;
}
这道题比上一道题人性化多了。。
给出k个点 两两之间建边 边的代价为原树上两两点之间的距离。求所有的代价总和 代价最小的边 代价最大的边。
显然我们是不能直接(k^2)暴力的 而这道题转换到树上也不过是一个树形dp的问题考虑 设f[x]表示以x为根的子树到x的代价和,g[x]表示到x这个点的个数
累计一下答案即可。至于第二问和第三问类似于直径和最短路径 也同样可以dp出来。
综上我们建出虚树 在树上完成路径的dp即可。
两问搞反了可还行,, 虚树边权打错了可还行。。。其实这道题就是普通的树形dp...谁知道我以前的代码为什么写的那么ex...
//#include<bitsstdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define INF 1000000000
#define ld long double
#define pb push_back
#define put(x) printf("%d
",x)
#define rep(p,n,i) for(RE int i=p;i<=n;++i)
#define pii pair<ll,ll>
#define F first
#define mk make_pair
#define mod 64123
#define RE register
#define get(x) x=read()
#define gt(x) scanf("%d",&x)
#define go(x) for(int i=lin[x],tn=ver[i];i;tn=ver[i=nex[i]])
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
RE int x=0,fa=1;char ch=getc();
while(ch<'0'||ch>'9'){if(ch=='-')fa=-1;ch=getc();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
return x*fa;
}
const int MAXN=1000010;
int n,len,Q,top,cnt,maxx,minn;
ll f[MAXN],w[MAXN];int g[MAXN],d1[MAXN],d2[MAXN],a[MAXN],vis[MAXN];
int s[MAXN],d[MAXN],dfn[MAXN],fa[MAXN][21],Log[MAXN],dis[MAXN];
int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1],e[MAXN<<1];
inline int cmp(int x,int y){return dfn[x]<dfn[y];}
inline void add(int x,int y,int z)
{
ver[++len]=y;
nex[len]=lin[x];
lin[x]=len;
e[len]=z;
}
inline void dfs(int x,int father)
{
fa[x][0]=father;d[x]=d[father]+1;dfn[x]=++cnt;
for(int i=1;i<=Log[d[x]];++i)fa[x][i]=fa[fa[x][i-1]][i-1];
go(x)if(tn!=father)dfs(tn,x);
}
inline int LCA(int x,int y)
{
if(d[x]<d[y])swap(x,y);
for(int i=Log[d[x]];i>=0;--i)
if(d[fa[x][i]]>=d[y])x=fa[x][i];
if(x==y)return x;
for(int i=Log[d[x]];i>=0;--i)
if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
inline void insert(int x)
{
if(top<=1){s[++top]=x;return;}
int lca=LCA(s[top],x);
while(top>1&&dfn[s[top-1]]>=dfn[lca])
{
add(s[top-1],s[top],d[s[top]]-d[s[top-1]]);
--top;
}
if(s[top]!=lca)add(lca,s[top],d[s[top]]-d[lca]),s[top]=lca;
s[++top]=x;
}
inline void dfs(int x)
{
w[x]=f[x]=g[x]=0;dis[x]=0;
d1[x]=-INF;d2[x]=INF;
if(vis[x])g[x]=1,d2[x]=d1[x]=0;
go(x)
{
dis[tn]=dis[x]+e[i];
dfs(tn);
maxx=max(maxx,d1[x]+d1[tn]+e[i]);
minn=min(minn,d2[x]+d2[tn]+e[i]);
d1[x]=max(d1[x],d1[tn]+e[i]);
d2[x]=min(d2[x],d2[tn]+e[i]);
f[x]+=f[tn];
f[x]+=(w[tn]+(ll)g[tn]*e[i])*g[x]+w[x]*g[tn];
g[x]+=g[tn];w[x]+=w[tn]+(ll)g[tn]*e[i];
}lin[x]=0;
}
int main()
{
freopen("1.in","r",stdin);
get(n);
rep(2,n,i)
{
int x,y;
get(x);get(y);
add(x,y,0);add(y,x,0);
Log[i]=Log[i>>1]+1;
}
dfs(1,0);get(Q);
memset(lin,0,sizeof(lin));
rep(1,Q,k)
{
int x;get(x);s[top=1]=1;len=0;
rep(1,x,i)get(a[i]),vis[a[i]]=1;
sort(a+1,a+1+x,cmp);maxx=-INF,minn=INF;
rep(1,x,j)if(a[j]!=1)insert(a[j]);
while(top>1)add(s[top-1],s[top],d[s[top]]-d[s[top-1]]),--top;
dfs(1);printf("%lld %d %d
",f[1],minn,maxx);
rep(1,x,i)vis[a[i]]=0;
}
return 0;
}
上一道我一直都不想做的题目。
LINK:luogu P4242 树上的毒瘤
题意不再赘述 显然还是上虚树 不过统计答案就显得尤为重要 求点对之间的答案。
难度有点大 可以dp 但是 应该也可以硬上点分治 考虑点对于点对之间的贡献 最后答案乘2即可 还有修改操作 外面套一个树剖。
虚树+点分治+树剖码量着实有点大 我考虑一种更容易的写法。
气死了 我写的题解和代码因为死机没了 这道题的点分治做法不讲了。
直接换根dp即可。考虑把路径拆分 一个点对需要-cnt+1。
暴怒写完此题。 一次AC怒切。
//#include<bitsstdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define INF 1000000000
#define ld long double
#define pb push_back
#define get(x) x=read()
#define gt(x) scanf("%d",&x)
#define put(x) printf("%d
",x)
#define rep(p,n,i) for(RE int i=p;i<=n;++i)
#define go(x) for(int i=lin[x],tn=ver[i];i;tn=ver[i=nex[i]])
#define pii pair<int,int>
#define F first
#define S second
#define mk make_pair
#define mod 1000000007
#define RE register
#define zz p<<1
#define yy p<<1|1
#define l(p) t[p].l
#define r(p) t[p].r
#define sum(p) t[p].sum
#define tag(p) t[p].tag
#define ls zz,l,mid
#define rs yy,mid+1,r
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
RE int x=0,f=1;char ch=getc();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
return x*f;
}
const int MAXN=100010;
int n,Q,len,cnt,cc,Top;
int a[MAXN],son[MAXN],sz[MAXN],fa[MAXN],id[MAXN],pos[MAXN],s[MAXN];ll g[MAXN],f[MAXN];
int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1],top[MAXN],d[MAXN],dfn[MAXN],vis[MAXN],e[MAXN<<1];
struct wy
{
int l,r;
int sum;
int tag;
inline wy(int a=0,int b=0,int c=0,int d=0){l=a;r=b;sum=c;tag=d;}
inline wy friend operator +(wy a,wy b){return wy(a.l,b.r,a.sum+b.sum-(a.r==b.l));}
}t[MAXN<<2];
inline int cmp(int x,int y){return dfn[x]<dfn[y];}
inline void add(int x,int y,int z=0)
{
ver[++len]=y;
nex[len]=lin[x];
lin[x]=len;
e[len]=z;
}
inline void dfs(int x,int father)
{
fa[x]=father;sz[x]=1;
d[x]=d[father]+1;dfn[x]=++cc;
go(x)
{
if(tn==father)continue;
dfs(tn,x);
sz[x]+=sz[tn];
if(sz[tn]>sz[son[x]])son[x]=tn;
}
}
inline void dp(int x,int father)
{
top[x]=father;
id[x]=++cnt;pos[cnt]=x;
if(!son[x])return;
dp(son[x],father);
go(x)if(tn!=son[x]&&tn!=fa[x])dp(tn,tn);
}
inline void build(int p,int l,int r)
{
if(l==r){l(p)=r(p)=a[pos[l]];sum(p)=1;return;}
int mid=(l+r)>>1;
build(ls);build(rs);
t[p]=t[zz]+t[yy];
}
inline void pushdown(int p)
{
r(zz)=l(zz)=tag(p);
r(yy)=l(yy)=tag(p);
tag(zz)=tag(yy)=tag(p);
sum(zz)=sum(yy)=1;
tag(p)=0;return;
}
inline void change(int p,int l,int r,int L,int R,int x)
{
if(L<=l&&R>=r)
{
sum(p)=1;
l(p)=r(p)=x;
tag(p)=x;
return;
}
int mid=(l+r)>>1;
if(tag(p))pushdown(p);
if(L<=mid)change(ls,L,R,x);
if(R>mid)change(rs,L,R,x);
t[p]=t[zz]+t[yy];
}
inline void Tchange(int x,int y,int z)
{
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(d[fx]<d[fy])swap(x,y),swap(fx,fy);
change(1,1,n,id[fx],id[x],z);
x=fa[fx];fx=top[x];
}
if(d[x]<d[y])swap(x,y);
change(1,1,n,id[y],id[x],z);
}
inline int LCA(int x,int y)
{
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(d[fx]<d[fy])swap(x,y),swap(fx,fy);
x=fa[fx];fx=top[x];
}
if(d[x]<d[y])swap(x,y);
return y;
}
inline wy ask(int p,int l,int r,int L,int R)
{
if(L<=l&&R>=r)return t[p];
int mid=(l+r)>>1;
if(tag(p))pushdown(p);
if(R<=mid)return ask(ls,L,R);
if(L>mid)return ask(rs,L,R);
return ask(ls,L,R)+ask(rs,L,R);
}
inline int Task(int x,int y)
{
wy b;
//cout<<x<<' '<<y<<' ';
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(d[fx]<d[fy])swap(x,y),swap(fx,fy);
b=ask(1,1,n,id[fx],id[x])+b;
x=fa[fx];fx=top[x];
}
if(d[x]<d[y])swap(x,y);
b=ask(1,1,n,id[y],id[x])+b;
//cout<<b.sum<<endl;
return b.sum;
}
inline void insert(int x)
{
if(Top<=1){s[++Top]=x;return;}
int lca=LCA(s[Top],x);
while(Top>1&&dfn[s[Top-1]]>=dfn[lca])
{
add(s[Top-1],s[Top],Task(s[Top-1],s[Top])-1);
--Top;
}
if(s[Top]!=lca)add(lca,s[Top],Task(lca,s[Top])-1),s[Top]=lca;
s[++Top]=x;
}
inline void dp(int x)
{
g[x]=vis[x];f[x]=0;
go(x)
{
dp(tn);
f[x]+=g[tn]*e[i];
f[x]+=f[tn];
g[x]+=g[tn];
}
}
inline void dfs(int x)
{
go(x)
{
f[tn]=f[x]-g[tn]*e[i]+(g[x]-g[tn])*e[i];
g[tn]=g[x];
dfs(tn);
}lin[x]=0;
}
int main()
{
freopen("1.in","r",stdin);
get(n);get(Q);
rep(1,n,i)get(a[i]);
rep(1,n-1,i){int x,y;get(x);get(y);add(x,y);add(y,x);}
dfs(1,0);dp(1,1);build(1,1,n);
memset(lin,0,sizeof(lin));
rep(1,Q,i)
{
int op,x;len=0;
get(op);get(x);
if(op==1)
{
int y;get(y);
Tchange(x,y,read());
}
if(op==2)
{
rep(1,x,j)sz[j]=get(a[j]),vis[a[j]]=1;
sort(a+1,a+1+x,cmp);
s[Top=1]=1;
rep(1,x,j)if(a[j]!=1)insert(a[j]);
while(Top>1)add(s[Top-1],s[Top],Task(s[Top-1],s[Top])-1),--Top;
dp(1);dfs(1);rep(1,x,j)printf("%lld ",f[sz[j]]+x),vis[sz[j]]=0;
puts("");
}
}
return 0;
}