提供一種和不太一樣的樹剖解法(一下考場就會做了qwq),儘量詳細講解。

思路

設重邊為黑色,輕邊為白色。

首先,先將邊的染色轉化為點的染色(即將 \(u\) 節點連向父節點的邊的顏色轉化為 \(u\) 節點的顏色)。

對於操作一,如果要把涉及到的點全部染色,顯然是不現實的。設染成顏色 \(1\) 的路徑為 \(x,y\),便容易得到一個結論:

除了 \(\text{LCA(x,y)}\) 會被染成白色以外,所有被染成白色的節點都是路徑上節點的子節點。

可以結合上圖理解一下。

也就是說,只要對於每個操作 \(1\) 給 \(\text{LCA(x,y)}\) 染成白色,以及給整個鏈的其他部分染上黑色,每個節點的顏色就只和節點本身與其父節點相關了。

此外,後來的操作會影響先前的操作,所以對於每個節點,我們需要儲存下每個節點最後被覆蓋成黑色的時間以及被覆蓋成白色的時間,然後這個東西看起來好像就可以用線段樹來維護了。

實現方式

在實現過程中,其實還有好多好多的問題要處理,這裡詳細講一下實現方法。

  • 邊權轉點權

    將邊權轉化為點權後,點 \(\text{LCA(x,y)}\) 雖然不用染成黑色,但是其子節點是需要被染成白色的(可以結合上文的圖輔助理解)。對於這種特殊情況,我們要同時將 \(\text{LCA(x,y)}\) 染成黑色和白色(非常離奇),也就是把黑色和白色的時間戳都更新成同一個時間。這樣處理後,仍然可以根據父節點和節點本身來判斷一個節點的顏色(見下文)。

  • 判斷節點顏色(重點)

    關鍵點來了,如何判斷一個節點的顏色?

    設父節點為 \(A\),子節點為 \(B\)。

    1.最後一次是染 \(A\) 且不是染 \(B\)

    且不是B 代表在染 \(B\) 的同時,沒有染 \(A\)(這可不是廢話哦)。根據後來操作覆蓋先前操作,\(B\) 的顏色完全取決於 \(A\)。而根據染色的方式,只要是染色的節點,一定處於染黑的鏈上(即使是點 \(\text{LCA(x,y)}\) 也沒關係,因為其子節點也要被染白),所以 \(B\) 一定是白色。

    2.最後一次是染黑 \(B\) 且不是染白 \(B\)

    這裡不需要考慮染 \(B\) 的同時有沒有染 \(A\),因為 \(B\) 的優先順序更高(考慮 \(A\) 相當於是間接染,考慮 \(B\) 相當於是直接染,按照操作優先順序可知)。

    \(B\) 在染成黑色的鏈上且不是 \(\text{LCA(x,y)}\),一定是黑色。

    3.最後一次是染黑 \(B\) 且也是染白 \(B\)

    說明 \(B\) 是 \(\text{LCA(x,y)}\),一定是白色。

在程式碼實現中,只需要判最後一次 是染黑 \(B\) 且不是染白 \(B\) 是否成立就好了。

  • 線段樹維護細節

    為了處理區間合併,每個節點應該儲存的資訊有:

    \(\text{l}_0,\text{l}_1,\text{r}_0,\text{r}_1,\text{data}\)

    即左端點最後一次被染成白色的時間、被染成黑色的時間,右端點最後一次被染成白色的時間、被染成黑色的時間,區間黑色點數量。這樣便可以處理區間合併時的邊界問題了。

    另外,由於 \(i\) 的顏色與 \(i-1\)(指 \(dfn\) 序)的相關,所以 \([l,r]\) 只能維護 \([l+1,r]\) 內的黑色點數量! 因此,在處理每個區間的黑色點數量時,還需要特殊處理左邊界是否為黑色點。

程式碼

常數很大,\(960ms\) 卡過去了。

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int maxn=100010;
inline int read()
{
register int x=0;
register char c=getchar();
for(;!(c>='0'&&c<='9');c=getchar());
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c&15);
return x;
}
int T;
int n,m,cnt,head[maxn],Cnt;
int fa[maxn],d[maxn],dfn[maxn],top[maxn],zson[maxn];
struct node
{
int u,v,to;
}e[maxn<<1];
void addedge(int u,int v)
{
e[++Cnt].u=u,e[Cnt].v=v;
e[Cnt].to=head[u],head[u]=Cnt;
}
struct tree
{
int l,r,data;
int lt[2],rt[2],lz[2];
}a[maxn*3];
void build(int i,int l,int r)
{
if(l>r) return ;
a[i].l=l,a[i].r=r;
a[i].data=0;
a[i].lt[0]=a[i].rt[0]=1;
a[i].lt[1]=a[i].rt[1]=0;
a[i].lz[0]=a[i].lz[1]=0;
//因為有多組資料,所以0也要賦值。
if(l==r) return ;
register int mid=(l+r)>>1;
build(i<<1,l,mid),build(i<<1|1,mid+1,r);
}
void pushdown(int i)
{
if(!a[i].lz[1]) return ;
a[i<<1].lz[1]=a[i<<1|1].lz[1]=a[i].lz[1];
a[i<<1].lt[1]=a[i<<1].rt[1]=a[i].lz[1];
a[i<<1|1].lt[1]=a[i<<1|1].rt[1]=a[i].lz[1];
a[i<<1].data=a[i<<1].r-a[i<<1].l;
a[i<<1|1].data=a[i<<1|1].r-a[i<<1|1].l;
a[i].lz[1]=0;
}
void add(int i,int l,int r,bool col,int time)
//將[l,r]區間最後一次染成col的時間覆蓋為time
{
if(a[i].l>=l&&a[i].r<=r)
{
a[i].lt[col]=a[i].rt[col]=time;
a[i].data=(col?a[i].r-a[i].l:0);
a[i].lz[col]=time;
return ;
}
if(a[i].l>r||a[i].r<l) return ;
pushdown(i);
add(i<<1,l,r,col,time),add(i<<1|1,l,r,col,time);
a[i].lt[col]=a[i<<1].lt[col],a[i].rt[col]=a[i<<1|1].rt[col];
register int Max=max(max(a[i<<1].rt[0],a[i<<1].rt[1]),max(a[i<<1|1].lt[0],a[i<<1|1].lt[1]));
a[i].data=a[i<<1].data+a[i<<1|1].data+(Max==a[i<<1|1].lt[1]&&Max!=a[i<<1|1].lt[0]);
//特判區間的邊界
}
int getsum(int i,int l,int r)
//[l+1,r]內黑點數量
{
if(a[i].l>=l&&a[i].r<=r) return a[i].data;
if(a[i].l>r||a[i].r<l) return -1;
pushdown(i);
register int x,y;
x=getsum(i<<1,l,r),y=getsum(i<<1|1,l,r);
if(x==-1) return y;
if(y==-1) return x;
register int Max=max(max(a[i<<1].rt[0],a[i<<1].rt[1]),max(a[i<<1|1].lt[0],a[i<<1|1].lt[1]));
return x+y+(Max==a[i<<1|1].lt[1]&&Max!=a[i<<1|1].lt[0]);
}
pair<int,int> getime(int i,int x)
//返回x位置上的點最後一次被染成白,黑色的時間
{
if(a[i].l==a[i].r)
return make_pair(a[i].lt[0],a[i].lt[1]);
pushdown(i);
if(a[i<<1].r>=x) return getime(i<<1,x);
else return getime(i<<1|1,x);
}
//以下dfs是樹剖
int dfs1(int fath,int x)
{
fa[x]=fath,d[x]=d[fa[x]]+1,zson[x]=0;
register int Max=-1,sum=1,xx;
for(register int u=head[x];u;u=e[u].to)
if(e[u].v!=fa[x])
{
xx=dfs1(x,e[u].v),sum+=xx;
if(xx>Max) Max=xx,zson[x]=e[u].v;
}
return sum;
}
void dfs2(int x)
{
dfn[x]=++cnt;
if(zson[fa[x]]==x) top[x]=top[fa[x]];
else top[x]=x;
if(zson[x]) dfs2(zson[x]);
for(register int u=head[x];u;u=e[u].to)
if(e[u].v!=fa[x]&&e[u].v!=zson[x])
dfs2(e[u].v);
}
void work(int time,int x,int y)
//將x到y的路徑染成黑色
{
while(top[x]!=top[y])
if(d[top[x]]>d[top[y]])
add(1,dfn[top[x]],dfn[x],1,time),x=fa[top[x]];
else
add(1,dfn[top[y]],dfn[y],1,time),y=fa[top[y]];
if(d[x]>d[y])
add(1,dfn[y],dfn[x],1,time),add(1,dfn[y],dfn[y],0,time);
else
add(1,dfn[x],dfn[y],1,time),add(1,dfn[x],dfn[x],0,time);
//別忘了將LCA再染成白色
}
pair<int,int>tt,t;
int solve(int x,int y)
//求x到y路徑上黑色點數量
{
register int sum=0,X,XX,Y,YY,Max;
while(top[x]!=top[y])
if(d[top[x]]>d[top[y]])
{
t=getime(1,dfn[top[x]]),tt=getime(1,dfn[fa[top[x]]]);
X=t.first,XX=t.second;
Y=tt.first,YY=tt.second;
Max=max(max(X,XX),max(Y,YY));
sum+=getsum(1,dfn[top[x]],dfn[x])+(Max==XX&&Max!=X);
//別忘了額外處理邊界(即'(Max==XX&&Max!=X)')
x=fa[top[x]];
}
else
{
t=getime(1,dfn[top[y]]),tt=getime(1,dfn[fa[top[y]]]);
X=t.first,XX=t.second;
Y=tt.first,YY=tt.second;
Max=max(max(X,XX),max(Y,YY));
sum+=getsum(1,dfn[top[y]],dfn[y])+(Max==XX&&Max!=X);
//別忘了額外處理邊界(即'(Max==XX&&Max!=X)')
y=fa[top[y]];
}
if(d[x]==d[y])
return sum;
if(d[x]>d[y])
{
y=zson[y];
t=getime(1,dfn[y]),tt=getime(1,dfn[fa[y]]);
X=t.first,XX=t.second;
Y=tt.first,YY=tt.second;
Max=max(max(X,XX),max(Y,YY));
sum+=getsum(1,dfn[y],dfn[x])+(Max==XX&&Max!=X);
}
else
{
x=zson[x];
t=getime(1,dfn[x]),tt=getime(1,dfn[fa[x]]);
X=t.first,XX=t.second;
Y=tt.first,YY=tt.second;
Max=max(max(X,XX),max(Y,YY));
sum+=getsum(1,dfn[x],dfn[y])+(Max==XX&&Max!=X);
}
return sum;
}
int main()
{
T=read();
while(T--)
{
memset(head,0,sizeof(head));
cnt=0,Cnt=0;
n=read(),m=read();
register int x,y,z,opt;
for(register int i=1;i<n;i++)
x=read(),y=read(),addedge(x,y),addedge(y,x);
dfs1(0,1),dfs2(1),build(1,1,n);
register int M=m+1;
for(register int i=2;i<=M;i++)
{
opt=read(),x=read(),y=read();
if(opt==1) work(i,x,y);
else printf("%d\n",solve(x,y));
}
}
return 0;
}

完結撒花~~

如果有什麼問題歡迎在評論區或者私信提出哦!