#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <sstream>
#include <map>
#include <set>
#include <queue>
#include <stdlib.h>
typedef long long ll;
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))

//k<<1(节点k的左子树下标)
#define lson l,mid,rt<<1
//k<<1|1(节点k的右子树下标)
#define rson mid+1,r,rt<<1|1

#define ll long long
const int N=100005;
int n,m,tot,cnt;
int first[N];
ll sum[N<<2],w[N],wt[N];
int d[N],fa[N],siz[N],son[N],top[N],id[N],rk[N];
struct node //节点:这道题没有边权值,所以没有w(价值)
{
    int v; //终点
    int nex;
}e[N<<1];

void adde(int u,int v)    //链式向前星,tot从0开始存,没有边权值,所以不用存
{
    e[tot].v=v;          //记录u节点的终点
    e[tot].nex=first[u]; //表示与第i条边同起点的下一条边存储的位置
    first[u]=tot++;
}
void init()
{
    tot = 0; //tot记录链式向前星
    cnt=0;
    memset(first,-1,sizeof(first)); //链式向前星的head数组,一般初始化为-1
    memset(son,0,sizeof(son));
}

void dfs1(int u,int pre,int depth) //处理 d,fa,siz,son 数组 -> 分别是深度、父亲、大小(包含自己本身)、重儿子节点
{
    d[u]=depth; //记录u节点的深度            -》深度
    fa[u]=pre;  //记录u的父亲节点            -〉父亲
    siz[u]=1;   //记录u的大小(这个点本身size=1) -》大小

    for(int i=first[u] ; i!=-1 ; i=e[i].nex) //遍历i节点
    {
        int v=e[i].v;          //v是是与i节点连接的点
        if(v==pre) continue;   //如果v是i的父亲节点,直接continue
        dfs1(v,u,depth+1);     //层次深度+1,以v为当前节点,u为父亲节点,深度+1
        siz[u]+=siz[v];        //父亲节点的大小=本身的大小+子节点的大小
        if(siz[v]>siz[son[u]]) //如果 子节点v的大小 比 父亲节点的重儿子节点的大小 大,那么更新重儿子节点
        {
            son[u]=v;
        }
    }
}

void dfs2(int u,int t) //当前节点,重链顶端 ——》处理 top(当前节点链的顶端) id(保存树中每个节点剖分呢以后的新编号) rk(对应节点) wt 数组
{
    top[u]=t;     //u节点的顶端是t

    /*id和rk正好相互对应*/
    id[u]=++cnt;  //保存树中每个节点剖分后的新编号
    rk[cnt]=u;    //保存当前dfs标号在树中所对应的节点

    wt[cnt]=w[u]; //储存当前的价值

    if(!son[u]) return; //如果是叶子节点(没有重儿子),直接返回

    /*如果不是叶子节点,一定是有重儿子的。
     我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/

    dfs2(son[u],t);

    for(int i=first[u];i!=-1;i=e[i].nex) //对重儿子进行遍历过后,再对当前节点的其余儿子进行遍历
    {
        int v=e[i].v;  //v是与i节点相连的节点
        if(v!=son[u] && v!=fa[u]) //如果v不是重儿子,且v不是u的父亲节点
        {
            dfs2(v,v); //一个点位于轻链底端,那么他的top必然是自己本身
        }
    }
}

//更新函数,这里是实现最大值,同理可以变成最小值,区间和等
void pushup(int rt)
{
    sum[rt] = sum[rt<<1] + sum[rt<<1|1]; //当前节点的sum=两个儿子节点的sum之和
}

/*每个叶子节点的值就是数组的值,每个非叶子节点的度都为2,且左右两个孩子分别存储父亲一半的区间。每个父亲的存储的值也就是两个孩子存储的值的最大值*/

//递归方式建树
void build(int l,int r,int rt) //建线段树,一开始l=1,r=n,rt=1,rt表示当前需要建立的节点
{
    if(l==r) //如果左端点==右端点,即为叶子节点,直接赋值即可
    {
        sum[rt]=wt[l];
    }
    else
    {
        int mid=(l+r)>>1; //m为中间点,左儿子的节点区间为[l,m],右儿子的节点区间为[m+1,r]
        build(lson); //递归构造左儿子节点
        build(rson); //递归构造右儿子节点
        pushup(rt);  //更新父节点
    }
}



ll query(int L,int R,int l,int r,int rt) // 查询[L,R]区间的和 ,l=1,r=n,rt=1
{
    if(l>=L && r<=R) return sum[rt]; //如果需要查询的区间在l和r之间,直接返回当前节点的sum值就可以了
    int mid=(l+r)>>1;
    ll ans=0;
    if(L<=mid) ans+=query(L,R,lson);
    if(mid<R) ans+=query(L,R,rson);
    return ans;
}

void update(int L,int R,int l,int r,int rt) //[L,R]区间开根号 ,rt为节点下标
{
    if(sum[rt]==r-l+1) return; //区间内所有点都为1,就不用再往下更新了
    if(l==r) //如果左端点==右端点,为叶子节点,直接更新它的值就可以了
    {
        sum[rt]=(ll)sqrt(sum[rt]); //
    }
    else
    {
        int mid=(l+r)>>1;
        if(L<=mid) update(L,R,lson); //如果需要更新的节点在左子树区间
        if(mid<R) update(L,R,rson);  //如果需要更新的节点在右子树区间
        pushup(rt);                  //更新父节点的值
    }
}

void uprange(int x,int y) //操作1 : x到y的最短路径开根
{
    while(top[x]!=top[y]) //如果x和y的顶端不相等,那么进行更新
    {
        if(d[top[x]]<d[top[y]]) swap(x,y); // 如果x顶端比y顶端深度要小(就是x在上面),交换x和y,就是保证x在下面
        update(id[top[x]],id[x],1,n,1);    // x顶端到x之间开根号
        x=fa[top[x]];                      // x=x顶端的父亲节点,x的值不断向上找,直到x和y拥有一样的顶端
    }

    //最后x顶端和y顶端相等
    if(d[x]>d[y]) swap(x,y);     //如果x的深度大于y的深度,那么交换x和y的值,保证x在下面
    update(id[x],id[y],1,n,1);   //x和y之间开根号
}

//查询操作,其实是个LCA,不过这里是用了top来进行加速,因为top可以直接跳转到该重链的起始节点,轻链没有起始节点之说,他的top就是它自己。
//需要注意的是,每次循环只能跳一次并且让节点深的那个来跳到top父亲节点的位置,避免两个一起跳从而擦肩而过
ll querange(int x,int y) //操作2 : 求x到y的最短路径的和
{
    ll ans=0;
    while(top[x]!=top[y]) //如果x和y的链顶端节点不相等,那么进行更新
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);  //如果x顶端比y顶端深度要小(就是x在上面),交换x和y,就是保证x在下面(保证x的节点深)
        ans+=query(id[top[x]],id[x],1,n,1); //求 x的顶端 到 x 之间的和(因为x顶端的id一定比x的id小)
        x=fa[top[x]];                       //x=x顶端的父亲节点
    }
    //直到x和y拥有一样的顶端为止
    if(d[x]>d[y]) swap(x,y);
    ans+=query(id[x],id[y],1,n,1);         //ans += x到y区间和
    return ans;
}

int main()
{
    scanf("%d %d",&n,&m); //树上的节点 + 操作数量
    init();               //初始化
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&w[i]); //将第i个节点的值输入
    }
    int u,v;
    for(int i=1;i<n;i++) //原来的树
    {
        scanf("%d%d",&u,&v); //输入两个节点,将两个节点连接起来(双向边)
        adde(u,v);
        adde(v,u);
    }

    dfs1(1,0,1); //第一遍dfs ,从根节点1开始,父亲是0,深度是1
    dfs2(1,1);   //第二遍dfs ,从根节点1开始,重链顶端也是1

    /*两遍dfs就是树链剖分的主要处理,通过dfs我们已经保证重链上各个节点dfs序连续。
     那么可以想到,我们可以通过数据结构,以线段树为例,来维护一条重链的信息。*/

    build(1,n,1); //建立线段树

    int op,x,y; //op是哪个操作,x,y分别是操作的两个点
    while(m--)
    {
        scanf("%d %d %d",&op,&x,&y);
        if(op==0) uprange(x,y); //开根号向下取整
        else if(op==1) printf("%lld\n",querange(x,y)); //计算x到y之间的和
    }
    return 0;
}