AtCoder Regular Contest 179 B,D問題メモ
B - Between B and B
問題文
- 1 以上 M 以下の整数からなる長さ M の数列 (X1,X2,…,XM) が与えられます.
- 1 以上 M 以下の整数からなる長さ N の数列 A=(A1,A2,…,AN) であって, 以下の条件を満たすものの個数を 998244353 で割ったあまりを求めてください.
- B=1,2,…,M について, A の中で異なる位置にある 2 つの B の間(両端を含む)には XB が存在する.
制約
- 1≤M≤10
- 1≤N≤104
- 1≤Xi≤M
解法
たとえば X1=5 のとき、一度 “1” を置いたら、“5” を置くまで、次に “1” をおくことはできない。
M の値が小さいのが取っかかり。
- DP[i,S]=(A1,A2,...,Ai) まで確定させて、Ai+1 には集合 S にある数字を置ける状態の数
たとえば、S={1,2,5,7} とすると、DP[i−1,S] の状態からは、Ai には4種類の数字を置ける。
i-1 i ... 1 3 6 ? ← 1 or 2 or 5 or 7 を置ける
Yi を、「Xj=i であるような j の集合」とし、前計算しておく。 (意味合い的には、i を置いたら次以降に置いて大丈夫になる数字)
Ai に k を置くとすると、DP[i,S∖{k}∪Yk]+=DP[i−1,S] というように遷移できる。
2M 通りにつき、“1”が立ってるbitからそれぞれ遷移できて、それを N 回繰り返すので、計算量は O(NM2M)
D - Portable Gate
問題文
- 頂点 1,2,…,N の N 頂点からなる木が与えられます. i 番目の辺は頂点 ui,vi を双方向に結んでいます.
- すべての頂点ははじめ白に塗られています.
- 駒と不思議なゲートを 1 個ずつ用いて次の手順ですべての頂点を黒に塗ります.
- まず好きな頂点を選び, 駒とゲートをその頂点に置きます.
- その後, すべての頂点が黒に塗られるまで次の操作を何度も行います.
- 次のうち 1 つを選んで実行する.
- 駒が置かれている頂点を黒に塗る.
- 駒が置かれている頂点に隣接した頂点をひとつ選び, その頂点に駒を移動させる, コストが 1 かかる.
- ゲートが置かれている頂点に駒を移動させる.
- 駒が置かれている頂点にゲートを移動させる.
- コストがかかるのは 2 番目の操作のみであることに注意してください.
- かかるコストの合計の最小値を求めてください.
制約
- 2≤N≤2×105
- 1≤ui,vi≤N
解法
要は、ゲームなんかであるファストトラベル的なイメージ。
既に訪れたことのある頂点を同時に1つまで登録でき、いつでもそこにノーコストで戻れる。
登録の上書きもできるが、そうすると以前登録されていた頂点は失われてしまう。
開始頂点が決まっている場合
開始頂点を根とした木DPで解ける。
探索は根から葉へ進む。基本的に、木の頂点を全て巡る移動といえばオイラーツアーのようになる。
ゲートは既に訪れた頂点を登録できるので、 オイラーツアーのコストの内、「ある頂点にゲートを設置しておき、その子の部分木を全て探索し終わったら、戻るのに必要なコスト」を節約するという使い方となる。
: ●←ゲート /\ ● ○ ゲートの位置まで戻るコスト3を、 /\ ゲートを設置することで節約できる ●● | ●←駒
(u) / \ (v) (w)
u にゲートを置き、v 以下の探索が終わったら戻るのに使おうと思うと、 v 以下の探索の過程ではゲートを1回も使うことはできない。
ただ、v では使わなくても、兄弟である w 以下では使うことができる。
w から v に移動するとき(または探索順は逆でもいいが)、u を経由するので、その時に再設置できる。
兄弟間は独立に、「親にゲートを設置して戻るのに使う」のか、「自分以下の部分木内で使う」のかを選べる。
(または、u より上位の頂点 x にゲートを設置する場合は v,w のいずれでも使うことはできないが、その場合のコストは、x でDPを計算する際にまとめて算出できる)
以下の木DPをする。
- DP[v]=(pv,qv,rv,sv)
- pv:v を出発して部分木を全て巡り v に戻ってくるのに、ゲートを使用した場合の最適コスト
- qv:v の部分木の頂点数
- rv:v を深さ0として、v の部分木で最も深い頂点の深さ
- sv:v を出発して部分木を全て巡り「v に戻ってこなくても良い」場合の最適コストの、pv との差分
問題の設定では、全てを黒で塗りおえたら、開始頂点に戻らずそこで終えてよい。
木DPでは、基本的には親に帰らないといけないので v まで戻るコストを考慮するが、
最後だけは、「全て塗りおえてから、根に戻るコスト」を省略できる。
sv は、それを考慮するものとなる。
頂点 DP[u] を求めたい場合、まず子ごとに寄与を計算する。
子 v が DP[u] に寄与する分をそれぞれ、p′uv,q′uv,r′uv,s′uv とする。
- p′uv の求め方
- v 以下でゲートを使う場合:u→v,v→u の部分は直接の移動が必要になる
- pv+2
- u にゲートを設置して、v の探索後戻るのに使う場合:
- v 以下はオイラーツアーなので、頂点数 qv×2 の直接移動が必要になる
- そのうち、最も深い頂点から帰る(そうなるように探索順を決める)ようにすると、rv+1 だけ省略できる
- 2qv−rv−1
- p′uv=min(pv+2,2qv−rv−1)
- s′uv の求め方
- まずは差分ではなく、最後の頂点を塗りおえるまでの最適コストを計算し、それを p′uv から引く
- v 以下でゲートを使う場合:u→v のみ新たな直接移動が必要になる(帰りは省略できる)
- pv−sv+1
- u にゲートを設置して、v の探索後戻るのに使う場合:上記と変わらない
- 2qv−rv−1
- s′uv=p′uv−min(pv−sv+1,2qv−rv−1)
これらを兄弟間で統合したものが、DP[u]となる。
- p′uv は、兄弟間で直接合算
- q′uv は、兄弟間で直接合算
- r′uv は、兄弟間でMAXを取る
- s′uv は、兄弟間でMAXを取る
最終的に、根を k として、pk−sk が、開始頂点を k とした場合の答えとなる。