Loading [MathJax]/jax/output/CommonHTML/jax.js

AtCoder Grand Contest 071 A,B 問題メモ

A - XOR Cross Over

問題文

  • 非負整数列が書かれている黒板があります。はじめ、黒板には長さ N の非負整数列 A=(A1,A2,,AN) のみが書かれています。
  • 黒板に書かれている非負整数列の長さがすべて 1 になるまで以下の一連の操作を行い続けます。
    • 黒板に書かれている非負整数列のうち、長さ 2 以上の非負整数列を 1 つ選び、黒板から消去する。
      • 選んだ非負整数列を B=(B1,B2,,Bn) とする。
    • 続けて 1k<n を満たす整数 k1 つ選び、以下の X,Y を求める。
      • X=B1B2Bk
      • Y=Bk+1Bk+2Bn
    • 最後に黒板に以下の 2 つの非負整数列を書き込む。
      • (B1Y,B2Y,,BkY)
      • (Bk+1X, Bk+2X,,BnX)
    • ただしここで、 はビット単位 XOR 演算を表します。
  • 最終的に黒板に書かれている N 個の長さ 1 の非負整数列を (C1),(C2),,(CN) としたとき、 Ni=1Ci として考えられる値の最小値を求めてください。

制約

  • 1N500
  • 0Ai<240
  • 入力される値はすべて整数

解法

問題文の理解に少しかかる。数列を2つに分割していき、分割の際、相手の総XORが自身の各要素にかかる。
元の A で同じ区間に由来する数列であっても、そこに至るまでの操作によって値が変わりうるので、素直な区間DPは使えない。

だが、実験するとほとんどは打ち消され、まぁ…単純とはいわないが、頑張れば求められる形になることが分かる。

各時点で黒板に書かれている数列は、元の (Al,Al+1,...,Ar) に 何らかの共通の値 z がXORされた形であり、(Alz,...,Arz) と表せる。

この「外部から数列全体にXORされる共通の値」をとりあえず変数 z としておいて、 分割したときにどうなるか、幅が小さい区間で考える。

※(a XOR b) を、単に ab と表すとする。

幅2                   分割候補            各XORの和
[ az  bz ]        →  [ az / bz ]         2 * ab

幅3
[ az  bz  cz ]    →  [ az / bz  cz ]     abcz + 2*bc
                  →  [ az  bz / cz ]     abcz + 2*ab
                                         ----------------
                                  最適値  abcz + 2*min(ab, bc)

幅4
[ az  bz  cz  dz] →  [ az / bz cz dz]    abcd + (z=aの時の[bz,cz,dz]の最適値)
                                        = 2*abcd + 2*min(bc,cd)
                                        
                  →  [ az bz / cz dz]    2*ab + 2*cd
                  
                  →  [ az bz cz / dz]    (z=dの時の[az,bz,cz]の最適値) + abcd
                                        = 2*abcd + 2*min(ab,bc)
                                        -------------------------
                                  最適値  min( 2*abcd+2*min(ab,bc,cd), 2*ab+2*cd )

幅5
[ az bz cz dz ez ] → [az/bz cz dz ez]    abcdez + (z=aの時の[bz,cz,dz,ez])
                                          ~~~~~~
                   → [az bz/cz dz ez]    2*ab + (z=abzの時の[cz,dz,ez])
                                        = 2*ab + abcdez + 2*min(cd,de)
                                                 ~~~~~~
                   → [az bz cz/dz ez]    (z=dezの時の[az,bz,cz]) + 2*de
                                        = abcdez + 2*min(ab,bc) + 2*de
                                          ~~~~~~
                   → [az bz cz dz/ez]    (z=eの時の[az,bz,cz,dz]) + abcdez
                                                                     ~~~~~~
                                        --------------------------------
                                  最適値  abcdez + (zに依らない値)

すると、以下の傾向があることに気付く。

  • 幅が偶数の時、最適値は z に依らない
  • 幅が奇数の時、最適値は「(XORz)+(z)

偶数の時は外部の影響を考えなくてよい。奇数の時も z は分割に依らず決まった形でのみ影響するので、z に依らない値のみで大小比較できる。
z を含む項はとりあえず無視して、それ以外での最適値で区間DPをする。

  • DP[l,r]:= 区間 [l,r) における、z を含む項を除いた最適値

DP[l,r] を求める時、区切り位置 m=l+1,...,r1 を全て試す過程で、

  • [l,r) の幅が偶数で、(偶,偶) に分割する場合
    • 幅が偶数の区間のDP結果は、そのままの値を使えばよい。
    • DP[l,r]minDP[l,m]+DP[m,r]
  • [l,r) の幅が偶数で、(奇,奇) に分割する場合
    • 幅が奇数の区間のDP結果は、z を考慮する必要がある。
    • (az,bz,cz,dz,ez,fz) を(3,3)に分割する場合、(az,bz,cz) には defz が、(dz,ez,fz) には abcz がXORされる。
      • ここで、z は互いに打ち消しあい、全ての要素から消滅する。
      • つまり、双方に abcdef の総XORがかかる。
    • DP[l,r]minDP[l,m]+DP[m,r]+2(A[l,r)XOR)
  • [l,r) の幅が奇数で、(偶,奇) に分割する場合
    • 最適値は DP[l,m]+DP[m,r]+(A[l,r)XORz) となる。
    • z を含む項は除くので、DPへ記録する値としては単純な足し合わせとなる。
    • DP[l,r]minDP[l,m]+DP[m,r]

これで区間DPを埋めていき、DP[0,N] が答え。

ただし、N が奇数の時は、最終結果でも z を含む項が省略されている。
A 全体に対する“外部”からの影響はないため、z=0 として、A の総XORを足したものが最終的な答えとなる。

Python3

B - Maximum Bracket Subsequence

問題文

  • 整数 N,K および (, ) からなる長さ K の正しい括弧列 S が与えられます。
  • (, ) からなる長さ N の文字列 T のうち、以下の条件をすべて満たすものの数を 998244353 で割った余りを求めてください。
    • T の部分列であって、長さ K の正しい括弧列であるものが存在する
    • T の部分列であって、長さ K の正しい括弧列であるもののうち、辞書順で最大のものは S である
  • なお、 (, ) からなる文字列の辞書順について、 () より小さい文字であるものとします。

制約

  • KN107
  • 2K5×105
  • N,K は整数
  • S は (, ) からなる長さ K の正しい括弧列

解法

あとちょっと。。。というところで時間切れ。C問題に浮気するんじゃなかった(結果論)。

サンプルが親切? で、ヒントを少しずつ出すように、サンプル1からサンプル4まで段階的に、 必要な考察ポイントを増やしながら用意してくれているような印象を受けた。

S に、辞書順最大のものを変えない範囲で、合計 NK 文字を追加することを考える。
「先頭から、今、開いている括弧の数」を「深さ」として、深さ 0 になる箇所で S を分割する。
分割された(両端を含む)箇所を「隙間」とする。

   (())(()())()
        ↓
   (())   (()())   ()
 ^      ^        ^    ^   隙間

隙間以外に、文字は追加できない。(「(())」の2-3文字目の間に「)」を追加するなど、結果的に隙間に追加したのと同じと見なせるようなものは除く)

    ( ? ( ? ) ? ( ? ) ? )    どの ? に置くにしても(結果的に隙間に追加したのと同じのを除けば)
                             より階層を低くできる=辞書順を大きくできる。
    ( ) (   )   (   )   )
→  ( ) (   )   (   )        どこに、どちらの向きで置いても、
      ~                 ~    置いた括弧を採用し、それと同じ向きの最も外の括弧を消した方が、
    (   (   )   ( ( )   )    必ず辞書順が大きくなる。
→      (   )   ( ( )   )
    ~             ~

よって、隙間への追加のみを考える。

S=()()()... の場合

まず、例外から片付ける。
長さ K のあらゆる正しい括弧列の中で辞書順最大のものは、「()()()()…」である。
もし S がこれの場合、T は「() が連続する箇所が K/2以上ある文字列」であればなんでもよい。

重複を除いてこれを数えるには、K/2+1 個ある隙間の、 前から K/2 箇所には「()」を含まない文字列、 最後の隙間には任意の文字列を置くようなパターンを数えればよい。
「()」を含まない文字列とは、「)))(((」のように 閉じ括弧の連続→開き括弧の連続 という形となる。いずれかまたは両方が長さ0でもよい。 つまり、閉じ括弧の個数、開き括弧の個数、2つの値を決めることで文字列が決まる。

前から K/2 箇所の隙間に合計何文字追加するか、を固定すると、

  • NKx=0KHx×2NKx

で、O(N) で求められる。

それ以外の場合

S のどこかには、深さ2以上の箇所が存在するとする。

隙間に「()」を含む文字列を追加することはできない。
もし追加すると、そちらを採用し、深さ2以上の箇所を1つ浅くした方が辞書順が大きくなる。
(辞書順最大にするためには、なるべく前の方にある深さ2以上の箇所を浅くすることになる)

S: (())    (()()) ()

T: (()) () (()()) ()
 → ()  () (()()) ()

T: (())    (()()) () ()   どこに () を追加しても、
 → ()     (()()) () ()   より辞書順の大きな部分列が作れてしまう。

各「隙間」におく文字列は、 必ず「)))(((」のように 閉じ括弧→開き括弧(いずれかまたは両方が長さ0でもよい)の文字列でなければならない。

さらに、先頭からはじめて深さが2以上になった箇所以降に追加する文字列は、全体でも部分列に「()」が含まれていてはいけない。

                はじめて深さ2以上になった箇所
                 ↓
S:   ()   ()   (()())   ()   (())
   ^    ^    ^        ^    ^      ^
                     ~~~~~~~~~~~~~~~
                     ここに追加する文字列は、全体でも () が含まれていてはいけない。

追加できる文字列の一例
S:   ()   ()   (()())   ()   (())
   ^    ^    ^        ^    ^      ^
  )(   ))(  )((      ))   ))(     ((((
  ~~   ~~~  ~~~      ~~~~~~~~~~~~~~~~~
  ↑   ↑   ↑       ↑ここは、全体が ))(( 型でないといけない
  |   |   |
  ここは、それぞれが ))(( 型ならよい

なぜなら、対応する括弧 (, ) が含まれていると、そちらを採用して深さ2以上の部分を1つ浅くした方が、辞書順が大きくなる。

S:   ()   ()   (()())   ()   (())
T:   ()   ()   (()()) ( ()   (()) )
                      ~           ~   正しい対応括弧を追加してしまうと
     ()   ()    ()()  ( ()   (()) )   こっちの方が辞書順が大きくなる。

以上が、S に追加してよい文字の条件である。

「① p=はじめて深さ2以上になる箇所以前の隙間の個数」「② q=以降の隙間の個数」を調べる。

①は、2p 箇所に置く文字数を決めれば良い。(p 箇所それぞれに「)」と「(」2種類ずつ)

②は、q+1 箇所に置く文字数を決めれば良い。(「)」と「(」が共存する隙間が1つ、あとは1種類ずつ)
ただし、どこで共存するかで q 通りあるので q 倍する。

さらに、それでは重複が発生するので除く。
隙間 i では「)))」のみ、隙間 i+1 では「(((」のみ、というように共存する箇所がない場合、 「共存する箇所」を、i としたものと i+1 としたもので、同じものが2回ずつ数えられてしまっている。

よって、以下が求める値となる。

  • 2p+q+1HNK×q2p+qHNK×(q1)

階乗の事前計算に O(N)p,q を求めるのに O(K) かかるので、全体 O(N) となる。

Python3

programming_algorithm/contest_history/atcoder/2025/0330_agc071.txt · 最終更新: 2025/04/04 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0