題意:
前言:
時間給的有點多,8秒,其實正解1秒足夠,8秒導致很多人只用了樹鏈剖分O(1)修改 暴力查詢都能過。
更好的解法是樹上樹套樹(學習別人代碼的)
時間複雜度O(Nlog^3N)
自己想到的做法是樹上動態主席樹,發現有點難實現(樹狀數組維護dfs序,線段樹維護權值 )發現不好做,
因爲樹上的節點並不是連續的,樹狀數組更新的時候dfs序又是連續的,可能會導致左兒子的點更新到右兒子的線段樹上面去了。
看了別人的代碼沒想到還可以反過來(樹狀數組維護權值,線段樹維護dfs序)
思想:樹剖解決,我們只維護每個節點和重兒子的邊權,
那麼當一個節點權值改變時,也只需要修改該節點和其重兒子的邊權,
若該節點是其父親節點的重兒子,則多修改一條該節點和其父親節點的邊權。查詢操作時,若該節點不是父親節點的重兒子,則暴力計算一次該節點和父親節點的邊權。
改爲:查詢操作時對所有小於k的樹狀樹節點查詢dfs序爲fa到u fa到v 的數量樹鏈剖分+樹狀數組+動態開點線段樹
時間複雜度O(Nlog^3N)
1、樹鏈剖分方便維護dfs的維護
2、樹狀數組是對每一條邊的權值進行維護 (gcd爲k)
3、動態開點線段樹真正的在維護每一個以k爲根節點的線段樹 的dfs序個數4、假設查詢小於k的,呢麼從樹狀數組那進行前綴查詢 樹狀數組又傳遞樹鏈剖分得到的dfs序區間
代碼就沒有自己打了,拿的別人的,有點長
/*
樹剖解決,我們只維護每個節點和重兒子的邊權,
那麼當一個節點權值改變時,也只需要修改該節點和其重兒子的邊權,
若該節點是其父親節點的重兒子,則多修改一條該節點和其父親節點的邊權。
查詢操作時,若該節點不是父親節點的重兒子,則暴力計算一次該節點和父親節點的邊權。
改爲:查詢操作時對所有小於k的樹狀樹節點查詢dfs序爲fa到u fa到v 的數量
樹鏈剖分+樹狀數組+動態開點線段樹
時間複雜度O(Nlog^3N)
樹鏈剖分方便維護dfs的維護
樹狀數組是對每一條邊的權值進行維護 gcd爲k
動態開點線段樹真正的在維護每一個以k爲根節點的線段樹 的dfs序個數
假設查詢小於k的,呢麼從樹狀數組那進行前綴查詢 樹狀數組又傳遞樹鏈剖分得到的dfs序區間
*/
#include<bits/stdc++.h>
#define PB push_back
using namespace std;
const int N=1e6+10,M=2e7+10,mod=998244353;
int n,q;
int a[N];
int w[N];
int cnt;
int son[N],siz[N],fa[N],de[N];
int dfn[N],top[N];
vector<int>G[N];
/*樹鏈剖分部分預處理*/
void dfs(int x){
siz[x]=1;
son[x]=-1;
for(int i=0;i<G[x].size();i++){
int &y=G[x][i];
if(!de[y]){
de[y]=de[x]+1;
fa[y]=x;
dfs(y);
siz[x]+=siz[y];
if(son[x]==-1||siz[y]>siz[son[x]])son[x]=y;
}
}
}
void dfs2(int x,int t){
dfn[x]=++cnt;
top[x]=t;
if(siz[x]==1)return;
dfs2(son[x],t);
for(int i=0;i<G[x].size();i++){
int &y=G[x][i];
if(y!=fa[x]&&y!=son[x])dfs2(y,y);
}
}
/*lca查詢*/
int lca(int x,int y){
while(top[x]!=top[y]){
if(de[top[x]]<de[top[y]])swap(x,y);
x=fa[top[x]];
}
if(de[x]>de[y])return y;
else return x;
}
/*動態開點權值線段樹部分*/
int rt[N],tot;
int s[M],ls[M],rs[M];
void update(int loc,int &x,int p,int l,int r){
if(x==0)x=++tot;
if(l==r){
s[x]+=p;
return;
}
int mid=l+r>>1;
if(loc<=mid)update(loc,ls[x],p,l,mid);
else update(loc,rs[x],p,mid+1,r);
s[x]=s[ls[x]]+s[rs[x]];
}
int query(int a,int b,int x,int l,int r){
if(x==0)return 0;
if(a==l&&b==r)return s[x];
int mid=l+r>>1;
if(b<=mid)return query(a,b,ls[x],l,mid);
else if(a>mid)return query(a,b,rs[x],mid+1,r);
else return query(a,mid,ls[x],l,mid)+query(mid+1,b,rs[x],mid+1,r);
}
/*樹狀數組部分*/
void upd(int pos,int k,int p){//某個dfs序pos,權值gcd:k p 1 或-1
for(;k<=1000000;k+=k&-k){
update(pos,rt[k],p,1,n);//k 這個線段樹
}
}
int que2(int l,int r,int k){//dfs序爲區間l,r信息
int ans=0;
for(;k;k-=k&-k){
ans+=query(l,r,rt[k],1,n);
}
return ans;
}
int que1(int u,int anc,int k){
int ans=0;
while(top[u]!=top[anc]){
ans+=que2(dfn[top[u]],dfn[u],k);//top[u]到u的
ans+=(__gcd(a[top[u]],a[fa[top[u]]])<=k);//*(top[u]==son[fa[top[u]]]);
u=fa[top[u]];
}
//cout<<"FUCK2 "<<anc<<' '<<u<<' '<<ans<<endl;
ans+=que2(dfn[anc],dfn[u],k);//一條鏈上了
return ans;
}
void que(int u,int v,int k){
int anc=lca(u,v);//最近公共祖先
//que1的ans+=(__gcd(a[top[u]],a[fa[top[u]]])<=k); 會重複計算,所以這裏要減去
int ans=-(__gcd(a[anc],a[fa[anc]])<=k)*2*(anc==son[fa[anc]])+que1(u,anc,k)+que1(v,anc,k);
printf("%d\n",ans);
}
int main()
{
//freopen("D://1.txt","r",stdin);
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1,u,v;i<n;i++){
scanf("%d%d",&u,&v);
G[u].PB(v);
G[v].PB(u);
}
de[1]=1;
dfs(1);
dfs2(1,1);
for(int i=1;i<=n;i++){
w[i]=__gcd(a[i],a[fa[i]]);
if(i==son[fa[i]])upd(dfn[i],w[i],1);
}
int op,u,v,k;
while(q--){
scanf("%d",&op);
if(op==1){
scanf("%d%d",&u,&k);
if(u==son[fa[u]]){
upd(dfn[u],w[u],-1);
a[u]=k;
w[u]=__gcd(a[u],a[fa[u]]);
upd(dfn[u],w[u],1);
}
else{
a[u]=k;
w[u]=__gcd(a[u],a[fa[u]]);
}
v=son[u];
if(v!=-1){
upd(dfn[v],w[v],-1);
w[v]=__gcd(a[u],a[v]);
upd(dfn[v],w[v],1);
}
}
else{
scanf("%d%d%d",&u,&v,&k);
que(u,v,k);
}
//for(int i=1;i<=n;i++)cout<<w[i]<<' ';cout<<endl;
}
return 0;
}