geam1113’s diary

主にAtcoderのコンテストの備忘録を書きます。AtCoder緑と水色を行ったり来たりしています。

ABC220F Distance Sums 2

与えられた木を、頂点1を根とする根付き木とみなします。

 

e=(u,v)で結ばれた頂点u,vを考えます。頂点uの方が根に近いものとします。

根からDFSをすると、vでの探索が終了した後にuに戻ります。

 

vを根とする部分木について、vの子孫w_1,...w_kについてのdis(v,w_1)+...+dis(v,w_k)が求まっているとします。

 

uから見た時、w_1,...w_kまでの距離は、vまでの距離にdis(u,v)=1が追加されたと考えることができます。式で表すと、dis(v,w)=dis(u,v)+dis(v,w)です。

dis(u,v)が何回足されるかを考えると、vの子孫の個数+1(vの分)となります。これはvを根とする部分木の頂点数に一致します。

 

したがって、根である1からの各頂点へ距離の総和は、以下の木DPにより求まります。

 

dp1[u]:=頂点1を根とする根付き木において、頂点uを根とする部分木の頂点数

 

dp2[u]:=頂点1を根とする根付き木において、頂点uを根とする部分木の、uとその子孫wとの距離dis(u,w)の総和

 

遷移を考えます。

dp1

uの子v_1,...,v_kについて、dp1[v_1],...,dp1[v_k]が求まっているとすると、

uを根とする部分木の頂点数は、子を根とする部分木の頂点数+u自身となるので、以下の通りです。

dp1[u] = dp1[v_1]+...+dp1[v_k]+1

 

dp2

uの子v_1,...,v_kについて、dp2[v_1],...,dp2[v_k]が求まっているとすると、uからすべての子孫への距離は、子であるv_iからの距離+dis(u,v)(=1)となり、dis(u,v)は、v_iを根とする部分木の頂点数分足されるので以下の通りです。

dp2[u] = dp2[v_1]+...+dp2[v_k]+dp1[v_1]+...+dp1[v_k]

 

このDPをDFSしながら解くと、根である頂点1からの各頂点の距離の総和は求まります。

しかし、今回「すべてを根とした時」について総和を求める必要があります。

この時に使えるのが全方位木DPです。

 

全方位木DPの詳細な解説は検索するとわかりやすい記事が出てくるので割愛します。

簡単に述べると、

 

(1) 頂点1を根とする木DPをする(DFSする)。

(2) 根でない頂点vについては、1回目のDFSで親である頂点uを根とするvでない側の部分木についてのみ、値が計算されていない

(3) そこで、uのうち、vが接続しているところを除いた部分木のDPの値を計算する

(4) 計算したDPの値をvに渡す

 

(3),(4)のアルゴリズムとしては、uの子v_1,...,v_kについて、dp_p=dp[v_1]+...+dp[v_{i-1}]+dp[v_{i+1}]+...+dp[v_k]を計算して、v_idp_pを渡します。この処理を頂点1から順に行なっていきます。(これはBFSになるらしいです。)

 

といったことをします。

 

全方位木DPでは、頂点uから見たときの頂点vを根とした時の部分木、というふうに見るため、以下のようなDPとなります。

 

dp1[u][v]:=頂点uを親としたときの、頂点vを根とする部分木の頂点数

 

dp2[u][v]:=頂点uを親としたときの、頂点vを根とする部分木の、vとその子孫wとの距離dis(v,w)の総和

 

uがあるかないかで、遷移はほぼ同じです。

 

全方位木DPでは、辺e=(u,v)に対し、始点側をuとすると、

vのDP情報はuが保存している

という点で、通常の木DPと異なると思います。

また、

(1) vが接続する頂点のDPの値を統合しつつ(uを除く)、何らかの計算を行い、

(2) その値から、vを含めたDPの値を計算し直し、

その計算値をu側に渡すことが必要になります。

(1)の処理だけでは、自分を含めた計算値になっていない場合があることにも留意します。

 

上記における、(1)の子の情報を統合して計算するのをop、(2)の自分を含めたDPの値を計算し直すのをadd_rootとして定義します。

 

今回の問題で実装を考えます。

uと接続するv_1,...,v_kについて考えます。

opではdp1,dp2共に、単純に子の情報を足し合わせていきます。

すると、最終的に

dp1_u=dp1[u][v_1]+...+dp1[u][v_k]

dp2_u=dp2[u][v_1]+...+dp2[u][v_k]

が計算された状態になっています。

 

この状態から、uに関する情報を計算し直すadd_rootでは、

dp2_udp1_u+1を加え、dp1_uに1を加えるとuについての正しいDPの値となります。

 

もう1点気をつけたいのが、全方位木DPでは、自分に接続するi番目の頂点の情報を除くために、

dpLeft_i = op(dp_1,...,dp_{i-1})
dpRight_i = op(dp_i,...,dp_k)

を予め計算値しておき、

op(dpLeft_i, dpRight_{i+1})によって、目的の値を得ます。

 

opはDFSでの子の情報の統合と、上記計算の3箇所で使用され、いずれも正しく値を計算できなければならないです。(コンテスト中、ここの実装でつまずいていました。)

 

自分の間違いとして、opについて、

op(dp1_i,dp1_j)=dp1_i+dp1_j

op(dp2_i,dp2_j)=dp2_i+dp2_j+dp1_j

としていましたが、これだと、op(dpLeft_i, dpRight_{i+1})を計算した時、不具合がおきます。

 

提出コード:https://atcoder.jp/contests/abc220/submissions/26256944

 

追記

ABC222Fの解説が全方位木DPについてよく書かれています。

https://atcoder.jp/contests/abc222/editorial/2749