1. 程式人生 > 實用技巧 >題解 CF916E 【Jamie and Tree】

題解 CF916E 【Jamie and Tree】

\[\text{前言} \]

\(\quad\)可以看看我的一篇blog關於樹鏈剖分"換根操作"筆記(內容都差不多)

\(\quad\)另外洛谷上還有一道關於換根操作的題目:P3979 【遙遠的國度】(我的題解)

\[\text{關於題目要求的操作} \]

\(\quad\)其實可以發現在一棵樹中,只有父親(祖先),兒子(子樹),深度等資訊會因為根節點的變化而變化,所以題目一般需要你有換根操作,子樹修改操作,求 \(LCA\) (最近公共祖先),我們分別來考慮一下。(可以看看下面這張圖來理解,題目中的圖)

)

\[\text{換根} \]

\(\quad\)因為每換一次根,樹中的很多資訊都會改變,不可能每次換根都跑兩便 \(dfs\)

預處理,所以我們考慮其他方法,對於單純的換根操作,只需要設定一個全域性變數 \(root\) 來儲存根的編號( \(root\) 初始化為 \(1\) ,預設以 \(1\) 為根),對於其他操作,再通過分類討論 \(root\) 的位置來進行操作。

\[\text{LCA(最近公共祖先)} \]

\(\quad\)因為這題我們肯定用樹鏈剖分解題,所以對於原圖( \(root==1\) )的情況下 \(LCA\) 的求法肯定是使用樹鏈剖分的(當然如果讀者願意專門打個倍增,那麼你們隨意)

\(\quad\)注意:(小寫) \(lca(x,y)\) 表示在以1為根的樹中 \(x\)\(y\) 的最近公共祖先,(大寫) \(LCA(x,y)\)

表示在以 \(root\) 為根的樹中 \(x\)\(y\) 的最近公共祖先。

il int lca(int x,int y) //模板樹鏈剖分求LCA
{
  int fx=top[x],fy=top[y];
  while(fx!=fy)
    {
      if(dep[fx]<dep[fy])swap(x,y),swap(fx,fy);
      x=father[fx];fx=top[x];
    }
  if(dep[x]>dep[y])swap(x,y);
  return x;
}

\(\quad\)接下來我們就要對 \(root\) 的位置進行分類討論了,程式碼先貼出來給你們看看。

il int LCA(int x,int y)
{
  if(dep[x]>dep[y])swap(x,y);
  int xr=lca(x,root),yr=lca(y,root),xy=lca(x,y);
  if(xy==x)
  {
  	if(xr==x){if(yr==y)return y;return yr;}
  	return x;
  }
  if(xr==x)return x;if(yr==y)return y;
  if((xr==root&&xy==yr)||(yr==root&&xy==xr))return root;
  if(xr==yr)return xy;
  if(xy!=xr)return xr;return yr;
}

\(\quad\)另外我們可以再畫幾張圖來方便理解。

一.當 \(lca(x,y)==x\) (可以先按深度調序, \(dep[x]<=dep[y]\))


\(\quad\) \(1\). 情況 \(1\)\(root\)\(x\) 的子樹中,也在 \(y\) 的子樹中,即 \(lca(x,root)==x\) && \(lca(y,root)==y\) ,此時 \(LCA(x,y)\)\(y\) ,因為圖要反過來看(以 \(root\) 為根)

\(\quad\) \(2\). 情況 \(2\)\(root\)\(x\) 的子樹中,但不在 \(y\) 的子樹中,即 \(lca(x,root)\) ,此時 \(LCA(x,y)\)\(lca(y,root)\)

\(\quad\) \(3\). 情況 \(3\) :其他情況下, \(LCA(x,y)\) 就是 \(x\)

二.當 \(lca(x,y)!=x\) (因為 \(dep[x]<=dep[y]\),所以 \(lca(x,y)!=y\)\(x\) , \(y\) 在不同子樹上)

\(\quad\) 1. 情況1:( \(lca(x,root)==x\) )||( \(lca(x,root)==x\) ),root在x(或y)的子樹中時, \(LCA(x,y)\)\(x\) (或 \(y\) ),顯然。

\(\quad\) 2. 情況2:( \(lca(x,root)==root\) && \(lca(x,y)==lca(y,root)\) )||( \(lca(y,root)==root\) && \(lca(x,y)==lca(x,root)\)),即 \(root\)\(x\)\(y\) 的簡單路徑上時,答案為 \(root\) 。(也可以用深度判斷, ( \(lca(x,root)===root\) && \(dep[root]>=dep[lca(x,y)]\) )||( \(lca(y,root)==root\) && \(dep[root]>=dep[lca(x,y)]\) ))

\(\quad\) 3. 情況3: \(lca(x,root)==lca(y,root)\) ,即 \(root\) 在上方時,\(LCA(x,y)\)\(lca(x,y)\)

\(\quad\) 4. 情況4:當 \(root\)\(x\)\(y\) 的鏈上節點的子樹中時, \(LCA(x,y)\) 為那個鏈上節點。

\(\quad\)這樣就把樹上所有 \(root\) 位置的情況都考慮到了,不重不漏。

\[\text{子樹修改(查詢)} \]

\(\quad\) 情況 \(1\) :當 \(x=root\) 時, \(x\) 就是此時整棵樹的根,那麼就是全域性修改(查詢)。

\(\quad\) 情況 \(2\) :當 \(root\) 在x子樹中時,就需要特別判斷了,根據影象我們可以發現此時x的真正子樹是包括除了 \(root\) 方向上的子樹之外其他所有節點。

\(\quad\) 情況 \(3\) :其他情況下 \(x\) 的子樹以 \(root\) 為根和以 \(1\) 為根是一樣的。

il int find(int x,int y)//尋找x中root所在的兒子節點
{
  int fx=top[x],fy=top[y];
  while(fx!=fy)
    {
      if(dep[fx]<dep[fy])swap(x,y),swap(fx,fy);
      if(father[fx]==y)return top[x];
      x=father[fx];fx=top[x];
    }
  if(dep[x]>dep[y])swap(x,y);
  return son[x];
}
il int query1(int x)
{
  int res=0;
  if(x==root){return query(1,1,n,1,n);}
  if(seg[root]>=seg[x]&&seg[root]<=seg[x]+size[x]-1){//判斷root在x的子樹中
    res+=query(1,1,n,1,n);int y=find(x,root);
    res-=query(1,1,n,seg[y],seg[y]+size[y]-1);
    return res;
  }
  return query(1,1,n,seg[x],seg[x]+size[x]-1);
}

\[\text{完整程式碼} \]

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
using namespace std;
#define int long long
#define next neee
#define re register int
#define il inline
#define inf 1e18
il int read(){
	int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)&&ch!='-')ch=getchar();
    if(ch=='-')f=-1,ch=getchar();
    while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    return x*f;}
il void print(int x)
{
	if(x<0)putchar('-'),x=-x;
    if(x/10)print(x/10);
    putchar(x%10+'0');}
const int N=2e5+5;
int n,m,next[N<<1],go[N<<1],head[N],tot,a[N],top[N],root;
int sum[N<<2],seg[N],rev[N],son[N],size[N],dep[N],father[N],c[N<<2];
il void Add(int x,int y)
{next[++tot]=head[x];head[x]=tot;go[tot]=y;}
il void dfs1(int x,int fa)
{
  father[x]=fa;dep[x]=dep[fa]+1;size[x]=1;
  for(re i=head[x],y;i,y=go[i];i=next[i])
    {
      if(y==fa)continue;
      dfs1(y,x);
      size[x]+=size[y];
      if(size[y]>size[son[x]])son[x]=y;
    }
}
il void dfs2(int x,int topf)
{
  top[x]=topf;seg[x]=++seg[0];rev[seg[x]]=x;
  if(!son[x])return;
  dfs2(son[x],topf);
  for(re i=head[x],y;i,y=go[i];i=next[i])
    {
      if(top[y])continue;
      dfs2(y,y);
    }
}
il void build(int k,int l,int r)
{
  if(l==r){sum[k]=a[rev[l]];return;}
  int mid=l+r>>1;
  build(k<<1,l,mid);build(k<<1|1,mid+1,r);
  sum[k]=sum[k<<1]+sum[k<<1|1];
}
il void ADD(int k,int l,int r,int v)
{sum[k]+=(r-l+1)*v;c[k]+=v;}
il void pushdown(int k,int l,int r,int mid)
{
  if(l==r){c[k]=0;return;}
  ADD(k<<1,l,mid,c[k]);ADD(k<<1|1,mid+1,r,c[k]);
  c[k]=0;}
il void change1(int k,int l,int r,int x,int y,int z)
{
  if(x<=l&&y>=r){ADD(k,l,r,z);return;}
  int mid=l+r>>1;
  if(c[k])pushdown(k,l,r,mid);
  if(x<=mid)change1(k<<1,l,mid,x,y,z);
  if(y>mid)change1(k<<1|1,mid+1,r,x,y,z);
  sum[k]=sum[k<<1]+sum[k<<1|1];
}
il int query(int k,int l,int r,int x,int y)
{
  if(x<=l&&y>=r)return sum[k];
  int mid=l+r>>1,res=0;
  if(c[k])pushdown(k,l,r,mid);
  if(x<=mid)res+=query(k<<1,l,mid,x,y);
  if(y>mid)res+=query(k<<1|1,mid+1,r,x,y);
  return res;
}
il int lca(int x,int y)
{
  int fx=top[x],fy=top[y];
  while(fx!=fy)
    {
      if(dep[fx]<dep[fy])swap(x,y),swap(fx,fy);
      x=father[fx];fx=top[x];
    }
  if(dep[x]>dep[y])swap(x,y);
  return x;
}
il int LCA(int x,int y)
{
  if(dep[x]>dep[y])swap(x,y);
  int xr=lca(x,root),yr=lca(y,root),xy=lca(x,y);
  if(xy==x){if(xr==x){if(yr==y)return y;return yr;}return x;}
  if(xr==x)return x;if(yr==y)return y;
  if((xr==root&&xy==yr)||(yr==root&&xy==xr))return root;if(xr==yr)return xy;
  if(xy!=xr)return xr;return yr;
}
il int find(int x,int y)//尋找x中root所在的兒子節點
{
  int fx=top[x],fy=top[y];
  while(fx!=fy)
    {
      if(dep[fx]<dep[fy])swap(x,y),swap(fx,fy);
      if(father[fx]==y)return top[x];
      x=father[fx];fx=top[x];
    }
  if(dep[x]>dep[y])swap(x,y);
  return son[x];
}
il void change2(int x,int z)
{
  if(x==root){change1(1,1,n,1,n,z);return;}
  if(seg[root]>=seg[x]&&seg[root]<=seg[x]+size[x]-1){//判斷root在x的子樹中
    change1(1,1,n,1,n,z);int y=find(x,root);
    change1(1,1,n,seg[y],seg[y]+size[y]-1,-z);
  }
  else change1(1,1,n,seg[x],seg[x]+size[x]-1,z);
}
il int query1(int x)
{
  int res=0;
  if(x==root){return query(1,1,n,1,n);}
  if(seg[root]>=seg[x]&&seg[root]<=seg[x]+size[x]-1){//判斷root在x的子樹中
    res+=query(1,1,n,1,n);int y=find(x,root);
    res-=query(1,1,n,seg[y],seg[y]+size[y]-1);
    return res;
  }
  return query(1,1,n,seg[x],seg[x]+size[x]-1);
}
signed main()
{
  n=read();m=read();
  for(re i=1;i<=n;i++)a[i]=read();
  for(re i=1;i<n;i++){re x=read(),y=read();Add(x,y);Add(y,x);}
  root=1;dfs1(1,0);dfs2(1,1);build(1,1,n);
  while(m--)
    {
      re k=read();
      if(k==1)root=read();
      if(k==2){re x=read(),y=read(),z=read();change2(LCA(x,y),z);}
      if(k==3){re x=read();print(query1(x));putchar('\n');}
    }
  return 0;
}

\(\quad\)碼題解不易,如果覺得不錯,不妨點個讚唄!