ツバサの備忘録

主に備忘録代わりに精進記録を載せていくつもりです。

ABC050 D - Xor Sum

問題
提出コード
方針がたってから遷移をバグなく求めるまでになんと半日かかりました!

解法

桁DPです。
というのも、2進数になおした2つの数字のペア(a,b)について、それぞれの桁についてありうる桁の組み合わせは、
(0,0), (0,1), (1,1)
の3種類のみです。
そして、これらの組み合わせが、そのまま(v,u)の組み合わせになります。
xorについては、(0,0)(1,1)ではどちらも0になりますが、(1,1)のパターンは、+をしたときにくりあがりをするため、(0,0)とは値が必ず異なります。
ということで、(0,0),(0,1),(1,1)の3種類の組み合わせを(a,b)のそれぞれの桁に当てはめていったときに、a+bNを下回るものの個数を求めれば、答えになります(a xor bはかならずa + b以下になるので、これ以降はxorについて考察をする必要はなくなります)。
ということで、(a,b)の組み合わせの中で、max(a,b)が最大となるのは、(a,b)=(N,0)のときになるので、Nを2進数に直した場合の桁数だけ、3種類のビットの組み合わせを当てはめていきます。
dp[i][j][k]とします。

  • i:左から今i桁目を見ている、という情報です。

  • j:i+1桁目でくりあがりが行われたかどうか、という情報です。真なら1、偽なら0です。

  • k:今作成している(a,b)は、a+b \lt Nとなることが確定しているかどうか、という情報です。真なら1、偽なら0です。

初期状態は、
dp[0][0][0] = dp[0][0][1] = dp[0][1][0] = 1
となります。これはそれぞれ、0桁目で(0,1)を選んだパターン(1桁目の繰り上がりなし)、i桁目で(0,0)を選んだパターン(1桁目の繰り上がりなし)、i桁目で(0,0)を選んだパターン(1桁目の繰り上がりあり)、の3つのパターンに対応します。


ここからは、0-indexedで考えていきます。例えば、2進数で110という数字は、左から0桁目と1桁目が'1'、2桁目が'0'です。
遷移の個数がかなり多いです。

  • Ni桁目が0か1か

  • (a,b)i桁目の組み合わせをどれにするか

  • i-1桁目の時点でjがどうだったか

  • i桁目のjがどうなるか

によって遷移が決まります。Ni桁目が0か1か、についてまずは考えていきます。

  • Ni桁目が'1'
    i-1の時点で、kが1ならばiでもkはそのままになります。kが0のとき、a+bi桁目が'0'になるような計算をしたときにkは1に変化し、'1'になるような計算をした場合はkは0のままです。

  • Ni桁目が'0'
    i-1の時点で、kが1ならばiでもkはそのままになります。
    kが0のとき、a+bi桁目が'0'になるような計算をしたときにkは0のままですが、'1'になるような計算をした場合はそもそもNを超えてしまうので、遷移ができません。

ということで、a+bi桁目が'0'になるか'1'になるかで、遷移が変わってくることがわかります。あとは、その他の遷移にかかわってくる条件をまとめて場合分けし、a+bi桁目がどうなるかを調べれば、遷移が確定します。


  • 3つの遷移に関わる条件について考え、a+bi桁目がどうなるかを調べる


(a,b,c,d)=
\left\{
\begin{array}{}
a,b:  (a,b)のi桁目の組み合わせ、(0,0),(0,1),(1,1)の3通り\\
c:     i-1桁目のjがどうであったか、0もしくは1 \\
d:    i桁目のjがどうなるか、0もしくは1
\end{array}
\right.
として、場合分けをしていきます。

(a,b,c,d)=(a+b)i桁目
というように表記し、羅列していきます。×は、そのようになる可能性がないことを示します。
(0,0,0,0) = 0
(0,0,0,1) = 1
(0,0,1,0) = ×
(0,0,1,1) = ×
(0,1,0,0) = 1
(0,1,0,1) = ×
(0,1,1,0) = ×
(0,1,1,1) = 0
(1,1,0,0) = ×
(1,1,0,1) = ×
(1,1,1,0) = 0
(1,1,1,1) = 1
です。あとは、これらとNi桁目による遷移の条件をもとに、ひたすら羅列をしていくことで、答えを求めることができます。

codeFlyer (bitFlyer Programming Contest)予選 C - 徒歩圏内

問題
提出コード
これを自力で解けなかったのは結構悔しいです…最近思い通りになかなか解けないですね

解法

X_j - X_i \leqq D,X_k - X_j \leqq D,D \lt X_k - X_i \leqq 2×Dとなるような(i,j,k)の組を探します。
要素が3つ存在するので、まずは真ん中を決め打ちしたときの(i,k)のペアの個数を求めることを考えます。
あるjについて、X_j - X_i \leqq DとなるようなiおよびX_k - X_j \leqq Dとなるようなkの個数は、それぞれ二分探索で求めることができます。

  • iの個数
    X_j - X_i \leqq Dを満たすことから、式変形をするとX_j  - D \leqq X_iを満たす必要があります。ということで、j-1までで、上の条件をみたすものの個数を調べるため、
    jから、X_i \lt X_j -Dとなるiの個数を引けばよいです。
    これは、二分探索を利用することで求められます。

  • kの個数
    X_k - X_j \leqq Dを満たすことから、X_k \leqq X_j + Dを満たす必要があります。
    ということで、j+1以降でこの条件を満たす区間の個数を求めるため、
    X_k \leqq X_j + Dとなるkの個数から、jを引けばよいです。

ということで、ikの個数を求められたので、(i,k)のペアの個数は、それぞれの個数の積になります。
ただし、このままだとD \lt X_k - X_iを満たさないものが存在しています。
ということで、最後にこうならないものを全体から引けば、答えが求まります。

  • D \geqq X_k - X_iとなる(i,j,k)の組の個数を求める
    D \geqq X_k - X_iとなる(i,k)のペアを決めたとき、ありうるji+1k-1です。
    今回は、kを決め打ったときに、上のD \geqq X_k - X_iをみたす(i,j)のペアの個数を求めていきます。
    D \geqq X_k - X_iとなるiの中で、最も遠いようなiをまず探します。
    これは、二分探索で求めることができます。これを仮にlとします。
    すると、(i,j)のペアは、今求めたl~今求めたk-1の中から2つの数字を選ぶ方法と同じになります。
    このとき、すべての(i,j,k)についてD \geqq X_k - X_iを満たしています。なぜなら、lがこの条件を満たすiの中で最も遠い場所になるので、それより後ろの場所をiとしても、X_kとの距離は小さくなるだけです。
    ということで、kから、X_i \lt X_k -Dとなるiの個数をP_kとしたときに、
    D \geqq X_k - X_iとなる(i,j,k)の組の個数は、
    \frac{(P_k)(P_k-1)}{2}
    となります。
    全てのkについてこれを求め、最初に数えたものから引いていけば、最終的な答えとなります。

ABC050 C - Lining Up

問題
提出コード
人生初の嘘解法を通しました!!!割と条件を省いて書いたのですが通ってしまったので、サンプルが弱いと思います(ACしない凡例が存在します)。
例えば、自分のコードだと

5  
0 0 0 2 2

という入力で答えが6になります(本来、このような入力の場合答えは0になるはずです)

解法

解法自体は正しいです(のはずです)
報告が正しいかどうかを判定したら、あとは答えが一意に決まります。
報告の判定方法は、

  • Nが偶数
    報告全てが奇数になっていて、報告された数字はすべて2つずつ存在しています。
    また、報告されるべき数字は1,3,5,7...N-1の奇数です。

  • Nが奇数
    報告される数字は偶数になっていて、0を除き報告された数字はすべて2つずつ存在しています。0は1回だけ報告されます。
    報告されるべき数字は0,2,4...N-1です。

上の条件を満たさないときは答えが0、満たすときは
2^{\frac{N}{2}}です(Nが奇数の場合は切り捨てをします)。
これを求めるだけで答えとなります。
正しい報告かどうかの判定をする部分の条件をかなり緩めにしたのですが、ACをしてしまいました。よくないです。

codeFlyer (bitFlyer Programming Contest)予選 D - ハンコ

問題
提出コード

解法

基本方針は、いもす法を利用して移動したときに黒くなる部分を求めていくことです。
紙の左上にまずはハンコを合わせます。すると、
(i,j)マスが黒かったとき、右にw-mマス、下にh-nマスの範囲内がすべて黒く塗られることになります。
ということで、これを1次元いもす法を2回適用、もしくは2次元のいもす法を適用することで黒く塗りつぶされている場所がわかります。
ですが、もちろんO(HW)が間に合うわけがないので、効率化します。 f:id:emtubasa:20181110175409p:plain 紙全体を図のように場合分けして考えます。

  • 白い部分
    1つ1つの四角は、多くて10^{6}マスなので、そのまま求めてしまって十分です。

  • オレンジの部分
    オレンジの部分は、どちらもw-2×m列存在します。
    元々のハンコの上からi行目のmマスは、それぞれのオレンジの部分についての上からi行目のマスを通過します。ということで、元々のハンコについて、i行目で1つでも黒い部分が存在していたら、オレンジの部分についてのi行目はすべて黒くなり、そうでなければすべて白くなっています。
    全てのiについて同じことが言えます。ので、オレンジの部分は1列に圧縮することができます。
    そして、最後にオレンジの部分を圧縮した1列の個数を、w-2×m倍することで、個数を数え上げることができます。

  • 青の部分
    青の部分は、どちらもh-2×n行存在します。
    オレンジの縦横逆バージョンです。なので、1行に圧縮することができ、最後にh-2×n倍してあげることで、数え上げることができます。

  • 黄色の部分
    この部分は、縦がh-2×n行、横がw-2×m列になっています。
    ハンコのn×mマスはすべてこの黄色の部分を少なくとも1度は通ります。
    ので、n×mマスのうち1つでも黒い部分が存在していれば、黄色の部分はすべて黒になります。
    黄色の部分を1マスに圧縮し、最後に(h-2×n)×(w-2×m)倍してあげると、この部分を数え上げることができます。

ということで、最終的には、
min(h,2×n+1)min(w,2×m+1)列の長方形についていもす法によって状態をつくりあげ、最後に圧縮している部分を拡大することで、答えを求めることができます。

AGC024 B - Colorful Slimes

問題
提出コード

解法

スライムを好きなタイミングで捕まえることができ、捕まえる、魔法を唱える、捕まえる魔法を唱える…といったような順番で操作を行うことができます。ので、魔法を唱えるを唱える回数kをまずは固定してしまって、捕まえるスライムの種類を決めてから、最後にk×xを追加します。
これをkについて全探索して最小値を求めることで答えを求めていきます。
k回魔法を唱えることにすると、0~k回のうち好きな回数変色ができます。
ということで、魔法を唱える回数が決まってしまっているので、i番目のスライムを入手したいとき、i-kiのスライムのうち一番入手時間が短いものを選んでくると、i番目のスライムを入手するために必要な時間を最小にすることができます。
区間についての最小値を求めたいので、RMQを使ってごり押しをします。
i-kが負になる場合を考慮するために、RMQは要素数2×Nにし、a_1a_Nを2つ並べます。すると、i+k-nは負になることがない(魔法を唱える回数がNより大きくなったときに答えとなることは絶対にありません)ので、配列外を参照することはなくなります。
ということで、あとは全てのiについて、区間[i-k,i+1)の最小値をRMQで求めて加算していき、最後にk×xを追加すればk回魔法を唱える場合の最小値になります。
これをkについて全探索することで、答えが求まります。

ARC077 D - 11

問題
提出コード

解法

ダブっている数字は数列に1つ必ず存在します。
ダブってる数字のうち左側にあるものをx,右側にあるものをyとしたとき、数列は次のようになっています。
 l, x, m, y, r
ここで、l,m,rはそれぞれ0個以上の要素を持っている数列です。
長さkの部分列を数え上げる方針としては、まず
l, x, m, r というyを除いたパターンを数え、その後yが存在した場合に新しく作成できるパターンを計算して合算します。

  • yを無視した場合
    l, x, m, r
    という数列には、1nの数字がすべて1つずつ含まれています。ので、全ての数字について、使うか使わないかの2通りを考えればよく、これは
    _nC_k
    となります。

  • yを考慮した場合
    新しく増えるパターンは、必ずyを使用します。なぜなら、使用しないパターンはすでに前の段階で数えているからです。
    ここは、さらに2つに場合分けします。xを部分列に含むか含まないか、です。


  1. xを含む場合
    x,yを使用することが確定しているので、残ったl,m,rの部分からk-2個を選びます。ということで
    _{(n-1)}C_{(k-2)}
    となります。

  2. xを含まない場合
    xyが同じ数字であるので、被りがないように数え上げなければなりません。
    ここで、どのように部分列を選んだらまだ数えられてない部分列(ここで数えるべき部分列)になるかというと、
    mの中から少なくとも1つの要素を選んだ場合
    です。
    lの要素は、xを使用した場合でもyを使用した場合でも、その数字より左側に、rはどちらの場合でも右側に存在します。
    ですが、mにある要素は、xを使用した場合はxの右側に、yを使用した場合ではyの左側に並ぶことになりますので、かならずまだ数えられてない、新しい部分列を作成できます。
    mの中から少なくとも1つの要素を選ぶことについて考えると、
    l,m,r全て使うパターン、m,rを使うパターン、l,mを使うパターン、mのみ使うパターンの4通りです。
    これをそれぞれ求めるのは難しいので、l,m,rの中から自由に選ぶパターンから、余分なパターンを引く、包除原理の考え方を使うことにします。
    l,m,rの中から自由に選ぶパターンの計算式をたてます。すでに決まっているのはyのみなので、l,m,rから残りのk-1個を選択します。l,m,rの個数をその文字で表現したとき、
    _{(l+m+r)}C_{(k-1)} となります。
    そして、余分なパターンは、mを使用しないパターンです。これは言い換えると、l,rのみから選んだパターン、となります。
    l,rから自由に選ぶパターンは、簡単に計算することができ、
    _{(l+r)}C_{(k-1)} です。あとは、上の式から下の式を引くだけでよく、
    _{(l+m+r)}C_{(k-1)} - _{(l+r)}C_{(k-1)} となります。

    ということで、最終的な式は次のようになります。
    _nC_k + _{(n-1)}C_{(k-2)} + _{(l+m+r)}C_{(k-1)} - _{(l+r)}C_{(k-1)}
    あとは、それぞれのkについてこれを求めれば、答えとなります。

ABC018 C - 菱型カウント

問題
提出コード

解法

ある地点で×な部分があると、その×を中心として作成したひし形の範囲内の任意のマス(x,y)について、(x,y)を中心とするひし形は作成できません。   ということで、
memo[i][j] = (i,j)を中心としてひし形を作成したとき、その範囲内に存在する×印の個数
とすると、 ということで、全ての×印のマスについて、その範囲内のマス目(x,y)についてmemo[x][[y]に1追加すれば、memoが完成します。
あとは、中心としてありうる(i,j)について、memo[i][j]=0となっているマス目を調べればよいです。
ここで、単純にmemo[x][y]に1を追加していたのでは間に合わないので、いもす法を用います。
(x,y)が×印だったとき、
memo[i + l][max(0, j - k + 1 + l)]には1を追加、memo[i + l][min(c, j + k - l)]からは1を減らす、という操作をしていけばよいです。
ここで、lは0~k-1になります。
あとは、横方向について累積和をとると、memoを完成させることができます。