ぺんぎんメモ

プログラミングのメモです。たまに私生活のことや鬱っぽいことを書きます。

AtCoder ABC152 F - Tree and Constraints

問題

https://atcoder.jp/contests/abc152/tasks/abc152_f

考察

次のようなDPを定義する。

dp[i][j] := 通り数

  • i : 頂点番号
  • j : 満たす制約の集合

あらかじめ、「ある辺を黒で塗ったときにどの制約を満たすか」を求めておく。その上で、根付き木上でDPを行う。木DPなので2種類の遷移を定義する必要がある。辺の持ち上げ操作とマージ操作。それぞれの遷移は次のようになる。

辺の持ち上げ操作

木DPでの親をu、子をv、それらを結ぶ辺をeとする。そして、辺xを黒く塗ったときに満たす制約の集合をC(x)とすると、遷移は次のようになる。

  • tdp[u][j] += dp[v][j]
  • tdp[u][j | C(e)] += dp[v][j]

tdpttempの略で、一時的なdp配列を表す。初期値はすべて0とする。

この遷移をjの状態数だけ行う。

マージ操作

マージ対象のdp配列の一方をldp、もう一方をrdpとすると、次のようになる。

  • dp[u][i | j] += ldp[u][i] * rdp[u][j]

これを素直に実装すると計算量O(22M)になるが、アダマール変換を使うことでO(M2M)に落とせる。

実装

https://atcoder.jp/contests/abc152/submissions/9845958

アダマール変換の実装は以下のページからお借りしました。

kazuma8128.hatenablog.com

感想

この問題を解く際に色々なことを考えた。

  • 木DPには遷移が2種類あるので、それぞれ別に考える(一緒に考えると頭の中がオーバーフローする)
  • 持ち上げ操作は+、マージ操作は*のようなもの
  • 各操作の単位元をどうするかについてきちんと考えると実装が楽になる(単位元を考えないとif文がひとつ増える。単位元を雑に考えるとバグる)
  • FFTさえ理解できれば、畳み込みのときに使われる様々な変換(メビウス変換、ゼータ変換、アダマール変換)がすぐに理解できるかもしれない。少なくとも、アダマール変換はすぐに使えた
  • 木のパス上の頂点はO(N)で列挙できる

想定解は包除原理とのこと。「補集合を数え上げる」という典型を完全に忘れていた。実際に実装すると物凄く楽だった。こちらの解法を思いつきたかった気もする。けれど、木DPとアダマール変換について学べたので良しとする。