- 给定一棵(n)个点的树,每个点有一个颜色(黑或白)。
- 你可以任选一个点出发,能执行两种操作:走到一个相邻点并翻转其颜色;翻转当前点颜色。
- 求使得所有节点颜色为黑的最少操作次数。
- (nle10^5)
换根(DP)
我们设(f_x)表示把(x)子树内全染黑且最终回到(x)的最少操作次数(方便起见不算入把(x)染黑的代价)。
由于我们不一定要走回(x),最后可以少走一条回到(x)的链,因此记录(g_x)表示从(x)子树内一点放弃回到(x)能减少的最大代价。
发现一个原本就全黑的子树我们不可能进入,因此再记录(p_x)表示一个子树内是否全黑,(w_x)表示(x)子树内非全黑的儿子个数的奇偶性。
最后我们再来遍换根(DP)即可。
转移式思维难度不高,但可能会存在一些恶心的细节,因此最好还是自己推一推,不要像我一样没想清楚就开始瞎写结果调了一个半小时。
代码:(O(n))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,a[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
I void read_a() {W(isspace(oc=tc()));for(RI i=1;i<=n;++i) a[i]=oc=='B',oc=tc();}
}using namespace FastIO;
int f[N+5],g[N+5],p[N+5],f_[N+5],g_[N+5],w[N+5];I void dfs1(CI x=1,CI lst=0)//初次DP
{
p[x]=a[x];for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(dfs1(e[i].to,x),!p[e[i].to])&&//只考虑非全黑子树
(w[x]^=1,p[x]=0,f[x]+=(f_[e[i].to]=p[e[i].to]?0:f[e[i].to]+(a[e[i].to]^w[e[i].to])+2));//更新w,p,f
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&!p[e[i].to]&&
(g[x]=max(g[x],g_[e[i].to]=g[e[i].to]+(a[x]^w[x])-(a[x]^w[x]^1)+1));//更新g(要用到w所以单独转移)
}
int ans;I void dfs2(CI x=1,CI lst=0,CI ff=0,CI gg=0,CI ww=0,CI pp=1)//换根DP
{
RI i,ff_=pp?0:ff+(a[lst]^ww)+2,gg_=pp?0:gg+(a[x]^w[x])-(a[x]^w[x]^1)+1;//求出子树外的转移贡献
ans=min(ans,f[x]+ff_+(a[x]^w[x]^!pp^1));//统计最后停在x的答案贡献
if(!pp) goto Calc;for(i=lnk[x];i;i=e[i].nxt) if(e[i].to^lst&&!p[e[i].to]) goto Calc;goto NCalc;//如果存在非黑子树才可能不停在x
Calc:ans=min(ans,f[x]+ff_+(a[x]^w[x]^!pp)-(max(g[x],gg_)-((a[x]^w[x])-(a[x]^w[x]^1))));NCalc:;//统计最后不在x的答案贡献
RI t=!pp+!a[x],Mx=gg_,Sx=0,Mp=0;for(i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
(t+=!p[e[i].to],g_[e[i].to]>Mx?(Sx=Mx,Mx=g_[Mp=e[i].to]):Sx=max(Sx,g_[e[i].to]));//求出最大值和次大值用于求新的g
RI nw,ng;for(i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(nw=w[x]^!pp^!p[e[i].to],//记录当前的w
ng=(e[i].to^Mp?Mx:Sx)-((a[x]^w[x])-(a[x]^w[x]^1))+((a[x]^nw)-(a[x]^nw^1)),//记录当前的g,更新当前点对g的贡献
dfs2(e[i].to,x,f[x]-f_[e[i].to]+ff_,ng,nw,!(t-!p[e[i].to])),0);//递归到子树DP
}
int main()
{
RI i,x,y;for(read(n),i=1;i^n;++i) read(x,y),add(x,y),add(y,x);read_a();
return ans=1e9,dfs1(),dfs2(),printf("%d
",ans),0;
}