【AT4133】[ARC097D] Monochrome Cat(换根DP)

点此看题面

  • 给定一棵\(n\)个点的树,每个点有一个颜色(黑或白)。
  • 你可以任选一个点出发,能执行两种操作:走到一个相邻点并翻转其颜色;翻转当前点颜色。
  • 求使得所有节点颜色为黑的最少操作次数。
  • \(n\le10^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\n",ans),0;
}
上一篇:[工具] nmap初步了解(二)


下一篇:【CF547E】Mike and Friends(AC自动机)