题解 P7163 [COCI2020-2021#2] Svjetlo

· · 题解

题面。

思路

很强的树形dp。

要求选择一个最短行走序列打开所有灯。

既然是序列,那就得有起点和终点。

考虑对序列的起点和终点分类讨论。

设dp中的当前点为 p,那么可以设三个状态:f_{p},g_{p},h_{p}。分别对应所选路径的起点终点都在 p 的子树之外/选路径的端点有一个在 p 的子树之外另一个在子树内/所选路径的起点终点都在 p 的子树内,打开 p 子树内所有灯的最小步数。

每个状态还要有额外的 0/1 一维,记录这个状态下当前点上灯的状态。

为了方便转移,f,h 状态本都有一进一出两步,但我们的状态中只记录一步,另一步在转移时再加上。

dp之前先记录一下哪些子树内的灯初始时已经全被打开了,这样的子树不需要被走过。

然后说状态转移。将某个子树的状态与当前信息合并,更新当前状态。

设当前要将状态合并到当前点的子树为to

f[p][0]=\min({f[p][1]+f[to][0]+2,f[p][0]+f[to][1]+4})\\ f[p][1]=\min({f[p][0]+f[to][0]+2,f[p][1]+f[to][1]+4})\\ \end{cases}

↑↑↑ 先说 f 的转移。

$+4$ 的转移:走一次 $+2$ 的转移后,再走两步:$p \rightarrow to \rightarrow p$。这样起到了在 $+2$ 转移基础上使 $p$ 和 $to$ 的灯泡状态都反转的效果。 $$\begin{cases} g[p][0]=\min({g[p][1]+f[to][0]+2,g[p][0]+f[to][1]+4,f[p][1]+g[to][1]+1,f[p][0]+g[to][0]+3})\\ g[p][1]=\min({g[p][0]+f[to][0]+2,g[p][1]+f[to][1]+4,f[p][0]+g[to][1]+1,f[p][1]+g[to][0]+3})\\ \end{cases}$$ ↑↑↑ 然后是 $g$ 的转移。 因为 $g$ 有一个端点在子树内而另一个端点在子树外,所以分端点在原有子树与端点在 $to$ 子树内分开讨论。状态也即 $f$ 与 $g$ 的合并。 $+2$ 的转移:原理与 $f$ 的对应转移类似。将子树 $to$ 的 $f$ 状态接在当前点的 $g$ 状态上。 $+1$ 的转移:将子树 $to$ 的 $g$ 状态接在当前点的 $f$ 状态上。此时该路径的起点在子树 $to$ 内,只有从 $to$ 点向上走到 $p$ 的额外一步,所以是加一。因为此转移只走过 $p$ 点一次而没有走过 $to$,所以 $to$ 的状态需要为 $1$ 而 $p$ 的状态正常取反。 $+4,+3$ 的转移:与 $f$ 的 $+4$ 的转移类似,在 $+2,+1$ 的基础上走到 $to$ 点再走回来一次。 $$\begin{cases} h[p][0]=\min({h[p][1]+f[to][0]+2,h[p][0]+f[to][1]+4,f[p][1]+h[to][0]+2,f[p][0]+h[to][1]+4,g[p][0]+g[to][1],g[p][1]+g[to][0]+2})\\ h[p][1]=\min({h[p][0]+f[to][0]+2,h[p][1]+f[to][1]+4,f[p][0]+h[to][0]+2,f[p][1]+h[to][1]+4,g[p][1]+g[to][1],g[p][0]+g[to][0]+2})\\ \end{cases}$$ ↑↑↑ 最麻烦的 $h$ 的转移。 $h$ 的两个端点都在子树内。所以有三种情况到达合并后的结果:原有子树内有两个端点而 $to$ 内没有;原树与 $to$ 子树内各有一个;两个端点都在 $to$ 子树内。 第一个 $+2$ 转移:对应上述第二个转移。原理与 $f$ 的 $+2$ 转移类似。将子树 $to$ 的 $f$ 状态接在当前点的 $h$ 状态上。 第二个 $+2$ 转移:对应上述第三个转移。将子树 $to$ 的 $f$ 状态接在当前点的 $h$ 状态上。同样是需要多走两步。 $+0$ 的转移:对应上述第二个转移。合并当前点的 $g$ 与 $to$ 的 $g$ 状态。因为 $g$ 状态本来就记录了离开当前点的一步,所以两个状态可以直接合并,并不需要额外走任何步数。 两个 $+4$ 的转移及第三个 $+2$ 的转移:与 $f$ 的 $+4$ 的转移类似,在上面三个转移的基础上走到 $to$ 点再走回来一次以实现 $p$ 点和 $to$ 点上状态都反转的效果。 $g$ 和 $h$ 的状态考虑了路径端点,所以不一定要从子树里转移而来,也有可能是以当前点为状态中路径的端点。 $$\begin{cases} g[p][0]=min(g[p][0],f[p][1]+1)\\ g[p][1]=min(g[p][1],f[p][0]+1)\\ h[p][0]=min(h[p][0],f[p][0])\\ h[p][1]=min(h[p][1],f[p][1])\\ \end{cases}$$ ↑↑↑ 四个以当前点为路径端点的转移。 对于 $g$ 的两个转移:设置当前点为起点。因为我们定义的 $f$ 状态中没有记录进入当前点的那一步,而以 $p$ 为起点后状态里少了起点那一步,所以要 $+1$,$f$ 的状态也对应反转。 对于 $h$ 的两个转移:设置当前点为一个端点。$g$ 与 $h$ 同样都只记录了离开当前点的一步,所以直接与 $f$ 状态取 $\min$ 即可。 初始选定的根需要其上的灯是关闭的,否则会通过一些不必要的步数将打开的灯关上再打开。最后的 $h_{root,1}$ 即为最终的答案。 #### 代码 ```cpp #include <cstdio> #include <algorithm> using std::min; const int Nx=500010; int N; char A[Nx]; struct edge{int to,nex;}; edge a[2*Nx]; int head[Nx],cnt; void add(int u,int v) { a[++cnt].to=v; a[cnt].nex=head[u]; head[u]=cnt; } int siz[Nx],saz[Nx],used[Nx]; void dfs_X(int p,int fa) { int i; siz[p]=1; saz[p]=A[p]; for(i=head[p];i;i=a[i].nex) { if(a[i].to==fa) continue; dfs_X(a[i].to,p); siz[p]+=siz[a[i].to]; saz[p]+=saz[a[i].to]; } if(siz[p]==saz[p]) used[p]=1; } int F[Nx][3],G[Nx][3],H[Nx][3];//i的子树内灯泡全部被点亮的最短路径长度 F该路径两端点都不在i子树内 G有一个端点在i子树里 H两个端点都在i子树里 void dfs_DP(int p,int fa) { //printf("DP : %d %d\n",p,fa); int i,to,f[3],g[3],h[3]; F[p][A[p]]=0; for(i=head[p];i;i=a[i].nex) { if(a[i].to==fa||used[a[i].to]) continue; dfs_DP(a[i].to,p); to=a[i].to; f[0]=F[p][0],g[0]=G[p][0],h[0]=H[p][0]; f[1]=F[p][1],g[1]=G[p][1],h[1]=H[p][1]; F[p][0]=min({f[1]+F[to][0]+2,f[0]+F[to][1]+4}); F[p][1]=min({f[0]+F[to][0]+2,f[1]+F[to][1]+4}); G[p][0]=min({g[1]+F[to][0]+2,g[0]+F[to][1]+4,f[1]+G[to][1]+1,f[0]+G[to][0]+3}); G[p][1]=min({g[0]+F[to][0]+2,g[1]+F[to][1]+4,f[0]+G[to][1]+1,f[1]+G[to][0]+3}); H[p][0]=min({h[1]+F[to][0]+2,h[0]+F[to][1]+4,f[1]+H[to][0]+2,f[0]+H[to][1]+4,g[0]+G[to][1],g[1]+G[to][0]+2}); H[p][1]=min({h[0]+F[to][0]+2,h[1]+F[to][1]+4,f[0]+H[to][0]+2,f[1]+H[to][1]+4,g[1]+G[to][1],g[0]+G[to][0]+2}); } G[p][0]=min(G[p][0],F[p][1]+1); G[p][1]=min(G[p][1],F[p][0]+1); H[p][0]=min(H[p][0],G[p][0]); H[p][1]=min(H[p][1],G[p][1]); } int main() { scanf("%d%s",&N,A+1); int i,j,k; for(i=1;i<=N;i++) A[i]-='0'; for(i=1;i<N;i++) { scanf("%d%d",&j,&k); add(j,k),add(k,j); } k=0; for(i=1;i<=N;i++) { F[i][0]=G[i][0]=H[i][0]=1e8; F[i][1]=G[i][1]=H[i][1]=1e8; if(k==0&&A[i]==0) k=i; } if(k==0) { printf("0\n"); return 0; } dfs_X(k,0); dfs_DP(k,0); printf("%d\n",H[k][1]); } ```