AOJ 0503 - Cup (Part 2)
これの続きです。このコードを短縮します。
まずは、配列pow3をわざわざ用意するのは長くなるので、毎回pow関数を使って値を求めることにします。
また、この問題の特徴として、#0と#2が入れ替わっても答えが変わらないことが挙げられます。そこで入力の際、#0→#1→#2の順に入力していましたが、これを#2→#1→#0の順にします。
以上をまとめると次のようになります。まだ長いですが、徐々に短くしていきます。
#include <stdio.h> #include <stdlib.h> #include <math.h> int c[16]; int n, m, i, j, k, d, x, p; int main(void){ for(; scanf("%d%d", &n, &m), n; printf("%d\n", x > m ? -1 : x)){ for(i = 3; i--;) for(scanf("%d", &j); j--; c[n-k] = i) scanf("%d", &k); p = 0; x = 0; for(i = n; i--; ){ d = abs(p - c[i]); x += pow(3, i) * d; if(d == 1){ p = 2 - p; } } x = fmin(x, pow(3, n) - 1 - x); } return 0; }
次に、全部の円盤の位置pについて考えます。pの値は0または2なので、pは非負整数qを用いて(q*2%4)と表すことができます。これはすなわち、qが偶数であれば#0、qが奇数であれば#2になるということです。ここで、qに値を加算すると、次のような変化があります:
- qに1を加算すると、qの偶奇が入れ替わります。すなわち、#0であった場合は#2に、#2であった場合は#0に移動することになります。
- qに0または2を加算しても、qの偶奇は変化しません。すなわち、全円盤の位置pは変化しません。
さて、各c[i]の値は0,1,2のいずれかですが、これが0または2であるときはpを変化させず、1であるときはpを変化させる必要がありました。これと上記の事項から、pを適切に移動させるためには、qにc[i]を加算するだけで良いことが分かります。つまり、実はif文を使わずに、以下のように簡潔に書くことが可能です。
#include <stdio.h> #include <stdlib.h> #include <math.h> int c[16]; int n, m, i, j, k, x, q; int main(void){ for(; scanf("%d%d", &n, &m), n; printf("%d\n", x > m ? -1 : x)){ for(i = 3; i--;) for(scanf("%d", &j); j--; c[n-k] = i) scanf("%d", &k); q = 0; x = 0; for(i = n; i--; ){ x += pow(3, i) * abs(q*2%4 - c[i]); q += c[i]; // 変更部分 } x = fmin(x, pow(3, n) - 1 - x); } return 0; }
さらに、qの初期値について考えます。前回記事ではpの初期値を#0としていましたが、入力部と同様、pの値は実は#0でも#2でも答えは同じになります。これをqについて考えると、実は、qの初期値は非負であれば何でも良いということになります。そこで上記のコードについて考えてみますと、入力に用いたkという変数は明らかに正の値であり、かつその後kを使うことはありません。従って、qとkを共用の変数とすることができ、さらにその初期化も不要ということになります。ここから、qを消去した次のコードが得られます。
#include <stdio.h> #include <stdlib.h> #include <math.h> int c[16]; int n, m, i, j, k, x; int main(void){ for(; scanf("%d%d", &n, &m), n; printf("%d\n", x > m ? -1 : x)){ for(i = 3; i--;) for(scanf("%d", &j); j--; c[n-k] = i) scanf("%d", &k); x = 0; for(i = n; i--; k += c[i]) x += pow(3, i) * abs(k*2%4 - c[i]); x = fmin(x, pow(3, n) - 1 - x); } return 0; }
次に、xについて考えてみます。ここで敢えて、z=(-1-x)という変数を定義し、xの代わりにzを計算してみることにします。このとき、x=(-1-z)=~z であることを利用すれば、次のようになります。
#include <stdio.h> #include <stdlib.h> #include <math.h> int c[16]; int n, m, i, j, k, x, z; int main(void){ for(; scanf("%d%d", &n, &m), n; printf("%d\n", x > m ? -1 : x)){ for(i = 3; i--;) for(scanf("%d", &j); j--; c[n-k] = i) scanf("%d", &k); z = -1; // zの初期値は-1 for(i = n; i--; k += c[i]) z -= pow(3, i) * abs(k*2%4 - c[i]); // ステップ数を減算 x = fmin(~z, pow(3, n) + z); } return 0; }
これはさっきより長いコードです。しかし、次のようにすれば短縮につながります。
入力が終わった段階で、変数jの値は-1となっているはずです。さらに、変数jはこの後使うことはありません。そこで、zの代わりにjを使うことで、zの初期化が不要となります。また、変数xについてもjを使えばいいので、xは消去できます。これよりコードは次のようになります。
#include <stdio.h> #include <stdlib.h> #include <math.h> int c[16]; int n, m, i, j, k; int main(void){ for(; scanf("%d%d", &n, &m) * n; printf("%d\n", j > m ? -1 : j)){ for(i = 3; i--;) for(scanf("%d", &j); j--; c[n-k] = i) scanf("%d", &k); for(i = n; i--; k += c[i]) j -= pow(3, i) * abs(k*2%4 - c[i]); //zの代わりにjをそのまま使う j = fmin(~j, pow(3, n) + j); } return 0; }
出力、すなわちprintfを実行する段階において、カウンタiの値は当然-1となっているはずです。従って、-1と書く代わりにiと書くことで、1byte短縮できます。
あとは、scanfが多いのがちょっと気になります。汚くなりますが、「scanf("%d",」の部分を#defineで1文字にまとめます。
最後に、余計な#includeやreturn、型名、空白などを取り除いて、次のコードが得られます。
#define S scanf("%d", c[]; main(n,m,i,j,k){ for(;S&n),S&m),n;printf("%d\n",j>m?i:j)){ for(i=3;i--;) for(S&j);j--;c[n-k]=i)S&k); for(i=n;i--;k+=c[i]) j-=pow(3,i)*abs(k*2%4-c[i]); j=fmin(~j,pow(3,n)+j); } }
改行およびインデントを取り除けば、195byteのコードが完成します。
いかがでしたでしょうか。今回は割と丁寧に書いたので、おそらく理解していただけたのではないかと思います。
この記事を読んだ方には、さらなる短縮に挑んで頂ければ幸いです。