geam1113’s diary

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

ABC233 参加記録

コンテスト中AC:A〜E

C - Product

ボールの数の総積が10^5を超えないので、各袋からボールを1つ選んだ時の全組み合わせも10^5を超えません。よって、得られる全ての積を配列に保存できます。

 

部分和DPの要領で、i番目の袋からボールを取った時に得られる全ての積の組み合わせを記録しておきます。そして、それらの全ての積に対して、i+1番目の袋のそれぞれのボールとの積を取ります。これをN番目まで繰り返します。

 

積の値は非常に大きいので、部分和DPのように配列のインデックスと積を一致させられません。そこで連想配列を使用します(C++のmap)。

連想配列は、ボールの積をキーに、その積の組み合わせ数を記録します。

 

気をつけたいのが、積がオーバーフローする場合です。

gccなら、__builtin_smulll_overflow関数でオーバーフロー検知できます。

 

提出コード

 

書いてみて、mapで確保するメモリの領域が多くなってしまう場合を考慮できていなかったことに気づきました。now,nextのように2つの配列を使い回した方が安全かもしれません。

また、全組み合わせが少ないので、同値をまとめなくとも普通の配列(C++vector)に、個別に記録しておいても良さそうです。

 

D - Count Interval

連続する部分列について、条件に一致するようにすべての区間を求める問題は以下に示すような典型的考え方があります。

S_iを、A_iまでの累積和、と定義します。便宜上S_0=0とします。

半開区間(j, i]のAの連続する部分列の和は以下のように示されます。

A_{j+1}+\cdots + A_i=S_i - S_j

よって、満たすべき条件は、

S_i - S_j=K

です。式を変形して、

S_i - K = S_j

したがって、iを末尾とする連続した部分列であって、条件を満たすものの個数は、上式を満たすjの個数となります。

 

そこで、以下のようなアルゴリズムで計算できます。

i=1〜Nについて、

・iまでの累積和S_iを得る

・これまでに出現した累積和のうち、S_i - Kと等しいものの出現数を答えに足す。

S_iの出現数を+1する

 

出現数の管理は連想配列やハッシュマップですればよいです。

また、最初に累積和0が1個必要なことに気をつけます。

それぞれは各iで計算できるので、計算量はハッシュマップならO(N)で、連想配列(C++のmap)なら、O(NlogN)です。

提出コード

 

E - Σ[k=0..10^100]floor(X/10^k)

筆算をイメージすると分かりやすいです。

 

X = 5891を例にします。
0は無視すると、求める和sumは、

sum=5891+589+58+5

です。説明のため、以下のように変形します。

sum=(5000+800+90+1)+(500+80+9)+(50+8)+5

= 5\times 10^3 + (5+8) \times 10^2 + (5+8+9) \times 10^1 + (5+8+9+1)\times 10^0

 

それでは、sumの1桁目と2桁目を求めてみます。

1桁目は(5+8+9+1)\times 10^0のみによって決定され、10の指数が0以外のものの影響を受けません。よって、23=2\times 10^1+3\times 10^0となり、1桁目は3です。

sum= 5\times 10^3 + (5+8) \times 10^2 + (5+8+9) \times 10^1 + 2\times10^1+3\times10^0

 

2桁目は、(5+8+9)\times 10^1と、1桁目で計算した時に発生した2\times 10^1との和によって決定されます。この時も、10の指数が1以外のものの影響を受けません。よって、340=3\times 10^2+4\times 10^1となり2桁目は4です。

sum= 5\times 10^3 + (5+8) \times 10^2 + 3\times 10^2+ 4 \times 10^1 +3\times10^0

 

以下同様にして3桁目以降も求められます。

 

このように、sumのi桁目は、前の桁からの桁上がりと、元々の10^iの係数の和のみによって計算できます。また、桁の重みである10の冪乗は無視できます。すなわち、先程の2桁目を求めるとき、(50+80+90)+20の2桁目を求める問題だったのを、(5+8+9)+2の1桁目を求める問題とみなすことができます。

これは、筆算で桁毎に独立に計算することと同じ意味です。

 

10^iの係数は以下のように、O(1)で計算できます。

Xの各桁を1桁の整数とみなした時に得られる整数列A=\{5,8,9,1\}とします。更に、ここから得られるA_iまでの累積和をS_iとします。

すると、最初の方に示した式は、以下のように書き変えられます、

= S_1\times 10^3 + S_2 \times 10^2 + S_3 \times 10^1 + S_4\times 10^0

 

このように、元々の10^iの係数は、Xの各桁を1桁の整数とみなしたときの累積和をとっておけば、O(1)で求められます。

 

具体的なアルゴリズムとしては、

XがN桁あるとし、桁上がりを示すcarry = 0とし、i = 1〜Nまで以下を繰り返します。

1.Y \leftarrow carry\ mod\ 10とする。

2. Z \leftarrow S_{N-i+1} + Yとする。 

3. Z\ mod\ 10sumのi桁目とする。

4.  carry \leftarrow \lfloor \frac{carry}{10} \rfloor + \lfloor \frac{Z}{10} \rfloorとする。

1番目の計算ですが、桁上がり値は、1つ上の桁より大きくなる可能性があるので、該当の桁のみ取り出しています。

 

気をつける点として、このアルゴリズムが終了した時点で、carry > 0なら、0となるまで上記アルゴリズムS_{N-i+1}を0にして実行する必要があります。

提出コード

 

上は10^iの係数を意識するため、書き直したものです。以下はコンテスト中の実装です。

carryは該当の桁のみを取り出さなくても計算できます。

1. Y \leftarrow S_{N-i+1} + carryとする。 

2. Y\ mod\ 10sumのi桁目とする。

3.  carry \leftarrow \lfloor \frac{Y}{10} \rfloorとする。

提出コード