2013年6月19日水曜日

本番: SRM583 Div1

Easy "TravelOnMars"

■時間
10分ぐらい?

■問題
リング上に街があり、各街からは最大で一定の距離まで行ける電車が走っている。任意の場所から場所まで移動した場合、乗る最小の電車の数はなにか。

■試した解き方
ただのDijkstra。modにちょっと気をつけるだけ。

■結果
無事System Test通過。

Medium "TurnOnLamps"

■時間
submitせず

■問題
木構造で、全てのedgeに電灯が設置されている。また電灯のうち何個かは「重要な」(=点いてほしい)電灯。電灯のON/OFFは木のノートを2つ指定すると、その間の最短パスの電灯が全て反転する。
重要な電灯が全てONになるためには最小で何回のオペレーションが必要か。

■試した解き方
だめぽ

■結果
submitせず

全体結果

レーティング: 1341→1407(+66)
青だとEasyできるだけでもそこそこ上がるのね。

筋トレ開始してから順調にまたレーティング上がり始めた。


反省

・Mediumはもうちょいで分かりそうなきがしたんだが・・・

2013年6月14日金曜日

本番: SRM582 Div1Easy "SpaceWarDiv1"

■時間
30分ぐらい?

■問題 SRM582 Div1 Easy "SpaceWarDiv1"
いろんな強さのモンスターがいて、モンスターを倒せる戦士がN人いる。戦士は自分以下の強さのモンスターは倒せるが、モンスターを倒すたびに「疲労度」が1点増える。モンスターを全員倒したときに一番疲労している戦士の疲労度が最小となるように倒していった場合、この疲労度の最小値はなにか。

■試した解き方
ちゃんと証明してない(ので今はあまり説明できない。あとでちゃんと埋める。)が、greedyで解ける:
戦士を弱い順にみていった時に、
今見ている戦士iの倒すべきモンスターは、残っているモンスターの総数をT、残った戦士の数をNとした場合、最大でもceil(T/N)。
 →それ以上倒しても、戦士iが倒せるモンスターは必ずiより強い戦士も倒せるので意味がない。

なので、
・戦士を弱い順にソート
・最大でceil(T/N)だけ自分以下の強さのモンスターを倒していく
・モンスターが残った場合は-1返す
(もしくは)一番多く倒した戦士の数を返す


public long minimalFatigue(int[] magicalGirlStrength, int[] enemyStrength, long[] enemyCount) {
Arrays.sort(magicalGirlStrength);
long numEnemyLeft=0;
for(long c:enemyCount)
numEnemyLeft+=c;
long max=0;
for(int girl=0;girl<magicalGirlStrength.length;girl++){
long norm=(long)Math.ceil(numEnemyLeft/(magicalGirlStrength.length-girl));
long tot=0;
for(int ei=0;ei<enemyCount.length;ei++){
if(enemyCount[ei]>0 && enemyStrength[ei]<=magicalGirlStrength[girl]){
long numDefeat=Math.min(norm, enemyCount[ei]);
tot+=numDefeat;
enemyCount[ei]-=numDefeat;
numEnemyLeft-=numDefeat;
norm-=numDefeat;
}
}
max=Math.max(max,tot);
}
if(numEnemyLeft>0)
return -1;
return max;
}

■結果
他の人と違う解き方をしていたせいでやたら狙われたが、無事System Test通った。
レーティング: 1221→1341(+120)
4回ぶりにレーティング上がった!

■Editorial
まだ載ってない。

■反省
・他の人は大体binary searchで最大値探してた。自分の方法はO(nt)で解けるので優秀だと思う。
・greedyの解法見つけたのはよかった。感覚的に証明出来る気がしたのでGOしたが、本来危険。



筋トレ8日目: SRM563 Div1Easy "FoxAndHandle"

■時間
一時間

■問題 SRM563 Div1 Easy "FoxAndHandle"
ある文字列S(eg: "ceil")の順番を入れ替えて(eg: "eilc")、元の文字列と組み合わせて作る文字列(eg: "ceeililc","ceileilc",...)について考える。
この時、ある目的の文字列H(eg: "fedcbafedcba")があった場合に、これを作れる元の文字列Sの中で一番辞書式順序の小さい文字列はなにか(eg: "afedcb")

■試した解き方
一瞬、分からなくなってとりあえずDPで解き始めたが、例えば"zzaabbcc"みたいなケースでは時間オーバーする可能性が高いと判断して断念。
ある場所である文字を選んだ時に、その右に残っている文字で、まだ選んでいない文字が全てカバーできるか、を判断するためのマトリクスを作ることを考える。
例えば文字列がzzaabbccの場合:
idx:01234567
S[idx]:zzaabbcc
a22100000
b22221100
c22222210
z10000000
まだ文字をなにも選んでいない時には必要文字数は(a=1, b=1, c=1, d=1)である。
マトリクスを見れば、最初の文字として「a」を選ぶことは不可能だということはO(m)(mは文字の数)で分かる。
(aが生じるidx(2,3)では、zの数がzの必要文字数(1)より低いため)
そのため最初に選び得る文字はzであることが判断できる。
(唯一idx:0ではマトリクスの残り文字数が必要文字数以上であるため)
以降、必要文字数がマトリクスの残り文字数以上となる出来るだけ低い文字を選んで文字列に足していくだけ。
O(nm^2)で計算できる。



public String lexSmallestName(String S) {
//文字の種類と数数える
char cs[];
int nums[];
{
char numForC[]=new char[27];
int numCs=0;
for(int i=0;i<S.length();i++){
if(numForC[(int)S.charAt(i)-'a']==0)
numCs++;
numForC[(int)S.charAt(i)-'a']++;
}
cs=new char[numCs];
nums=new int[numCs];
int j=0;
for(int i=0;i<numForC.length;i++){
if(numForC[i]>0){
cs[j]=(char)(i+'a');
nums[j]=numForC[i]/2;
j++;
}
}
}
//各idxより右にある各文字の数
int existenceMatrix[][]=new int[cs.length][S.length()];
for(int ci=0;ci<cs.length;ci++){
char c=cs[ci];
int num=nums[ci]*2;
for(int si=0;si<S.length();si++){
if(S.charAt(si)==c)
num--;
existenceMatrix[ci][si]=num;
}
}
boolean used[]=new boolean[S.length()];
StringBuffer buf=new StringBuffer();
int atIdx=0;
//文字列が埋まるまで
while(buf.length()<S.length()/2){
//いい文字をなるべく低い順に選ぶ
int ci;
int cPositionIdx=0;
for(ci=0;ci<cs.length;ci++) //いい文字とは・・・
if(nums[ci]>0){ //そもそも足す必要があり、
nums[ci]--;
boolean found=false; //マトリクスで利用可能文字数が必要文字数以上になる
for(int si=atIdx;si<S.length();si++){
if(S.charAt(si)==cs[ci] && !used[si]){
boolean remainingOK=true;
for(int j=0;j<cs.length;j++)
if(existenceMatrix[j][si]<nums[j]){
remainingOK=false;
break;
}
if(remainingOK){
cPositionIdx=si;
used[cPositionIdx]=true;
found=true;
break;
}
}
}
if(found)
break;
nums[ci]++;
}
//マトリクス更新(要らない)
for(int i=0;i<cPositionIdx;i++)
existenceMatrix[ci][i]--;
atIdx=cPositionIdx+1;
buf.append(cs[ci]);
}
return buf.toString();
}

1st submit:
OK

■Editorial
マトリクスをわざわざ事前に作ってはいないが、同じ解き方

■反省
・最初DPで解こうと思ってしまったのが誤り。
・一時間ぎりぎりでコーディング+サンプルテストを通ったので、自分で色々テストする暇なくsubmitしてしまった。
・大会でのsuccess rateが60.4%なので、結構難しい問題だった?

■筋トレ記録
勝率67% (6/9)

2013年6月13日木曜日

筋トレ7日目: SRM564 Div1Easy "KnightCircuit2"

■時間
25分

■問題 SRM564 Div1 Easy "KnightCircuit2"
ボードの大きさw,hを渡された時、任意の初期位置から始めてチェスのナイトが自由に動いていった場合に行ける場所の最大数はなにか

■試した解き方
あるボードw,hで全部の場所に行ける場合、(w+1),hでもw,(h+1)でも必ず全部の場所に行けることに注目。(w,h>=2の場合だが、そもそも全部の場所に行けるのは(3,4)が最初なので関係ない)
なので小さいボードに関しては場合分けしてしまい、大きいボード((3,4)以上の)はw*hを返せばいい。

public int maxSize(int w, int h) {
if(w>h){
int t=h;
h=w;
w=t;
}
if(w==1)
return 1;
else if(w==2)
return (h+1)/2;
else if(w==3 && h==3)return 8;
return w*h;
}

1st submit:
OK

■Editorial
同じ解き方。

■反省
・なにこれ本当にdiv1?
・とはいいつつ、最初「(w,h)がcompleteなら(w+1,h)もcomplete」ということに気づかず、戸惑ってしまった。

■筋トレ記録
勝率63% (5/8)

2013年6月12日水曜日

筋トレ7日目: SRM565 Div1Easy "MonstersValley"

■時間
25分

■問題 SRM565 Div1 Easy "MonstersValley"
各地点i(1<=t<=T)におけるモンスターの強さdread[i]と、買収した場合の値段price[i](price[i]∈{1,2})があった場合に、1からTまで無事に通過するための最小の値段を返せ。
ただし、強さdread[i]のモンスターに遭遇した時は、それまでに買収したモンスターの合計以下の強さの場合は素通り出来る(買収もできる)。合計より強い場合は買収しないといけない。
尚dreadは1〜10^12、Tは1〜50。

■試した解き方
Tが大きいためbrute forceは当然無理。
priceが1か2しかないことに着目。最大でもT*2しか払わなくていいということ。
各地点iにおいて、各今まで払った額p[i]毎に、仲間モンスターの一番高い強さを計算していけばO(T^2)で解ける
public int minimumPrice(long[] dread, int[] prices) {
int maxPrice=dread.length*2;
Long largestTotalFor[][]=new Long[dread.length][maxPrice+1];
largestTotalFor[0][prices[0]]=dread[0];
for(int i=1;i<dread.length;i++){
int priceForI=prices[i];
for(int price=0;price<=maxPrice;price++){
Long total=null;
//buy?
if(price-priceForI>=0 && largestTotalFor[i-1][price-priceForI]!=null)
total=largestTotalFor[i-1][price-priceForI]+dread[i];
//walk through?
if(largestTotalFor[i-1][price]!=null && largestTotalFor[i-1][price]>=dread[i])
if(total==null || largestTotalFor[i-1][price]>total)
total=largestTotalFor[i-1][price];
largestTotalFor[i][price]=total;
}
}
for(int i=0;i<=maxPrice;i++)
if(largestTotalFor[dread.length-1][i]!=null)
return i;
return 0;
}

1st submit:
OK

■Editorial
同じ解き方。

■反省
・苦手なdynamic programming系の問題を解けて満足。

■筋トレ記録
勝率57% (4/7)

2013年6月11日火曜日

筋トレ6日目: SRM566 Div1Easy "PenguinSledding"

■時間
一時間

■問題 SRM567 Div1 Easy "PenguinSledding"
グラフが渡された時、二次元空間上にどのようにマッピングしても(vertexをどこに置いても)edgeが重なる事の無いように、edgeを消したい。
何パターンありえるか?

■試した解き方
要は、有効な形は意外と少なく、「edgeなし」「edge一本だけ」「あるvertexから星形にedgeが出ている」だけ。
(実はこれは間違いで、三角形というパターンもあった)
なのでこれらを一つ一つ数えるだけ。

1st submit:
自分に設けた1時間の時間制限を超えたため、最後のsampleが間違っていたもののsubmit。

単純な、1,{1,2,3},{2,3,1}ケースを間違えていたことで三角形のパターンもあると気づいた。

2nd submit:
OK

■Editorial
チラ見しかしてないが、大体同じ解き方。
自分がΣ[2<=i<=n] nCi みたいな計算をわざわざしていたところを(2^n)として計算していて愕然とした。なるほど。

■反省
・今回は、優しいsample caseに救われた(結局駄目だったけど)
 →L字型だけ考えればいいのかと思ったら*型もあることにsample3で気づく
 →かと思いきや、三角形のケースが含まれるsampleにつまずく
・ここまで優しいsampleが用意されている問題は少ないが、どうしたら網羅的に考えられたんだろう?

■筋トレ記録
勝率50% (3/6)

2013年6月10日月曜日

筋トレ5日目: SRM567 Div1Easy "TheSquareRootDilemma"

■時間
一時間以上

■問題 SRM567 Div1 Easy "TheSquareRootDilemma"
関数SSR(A,B)=(√A + √B)^2 を定義した時、
1<=A<=N, 1<=B<=M, SRM(A,B)は整数
を満たす(A,B)の数を返せ

■試した解き方
Sieve of Eratosthenes風に、(1,1)から始めて偶数個の素数をAとBにかけ算していく。

1st submit:

最大値のN,M=77777でも自分の環境では500msで実行できたので安心していたのだが、
timeout。

沢山submit後:
longを全て消して、なんとか通った。


■Editorial
Aは普通にforで回し、それに対応してSRM(A,B)が整数になるBを数えていくという方式。

やってることは同じだけどfor文2つで書けてるのが奇麗。

■反省
・もうちょっと数式いじってればeditorialと同じ方式思いついたと思う。
・自分の環境で上手く行っても安心するな。

■筋トレ記録
勝率60% (3/5)

2013年6月9日日曜日

筋トレ4日目: SRM568 Div1Easy "Balls Separating"

■時間
8:25〜8:45

■問題 SRM568 Div1 Easy "Balls Separating"
赤、青、緑のボールが最低一つずつ入った箱がn個ある。

箱から箱へ任意のボールを一つずつ移動させる時、
どの箱にも異なる色のボールが入っていない状態を作るために最小の移動数を返せ。

■試した解き方
まず赤、青、緑のボールそれぞれの受け入れ口が最低一箱ずつ必要なことに着目。
超単純に、赤の受け入れ口、青の受け入れ口、緑の受け入れ口を全パターン網羅し、
各パターンにおいて下記ボール割り振りを行う。
ボールの割り振りは、各箱において「一番多い数のボールだけを残す」のが最小移動数になる。
なので、各箱で最多色以外の和を積算していき、前記赤,青,緑の全パターンの中で最小の積算値を返す。
1st submitでsystem test通った。


public int minOperations(int[] red, int[] green, int[] blue) {
int n=red.length;
boolean hasRed=false, hasGreen=false, hasBlue=false;
for(int i=0;i<n;i++)
if(red[i]>0){
hasRed=true;
break;
}
for(int i=0;i<n;i++)
if(green[i]>0){
hasGreen=true;
break;
}
for(int i=0;i<n;i++)
if(blue[i]>0){
hasBlue=true;
break;
}
{
int numOK=(hasRed?1:0)+(hasGreen?1:0)+(hasBlue?1:0);
if(numOK>n)
return -1;
}

int minSum=Integer.MAX_VALUE;
//choose a red, green, and blue container
{
for(int ri=0;ri<n;ri++){
if(red[ri]>0 || !hasRed){
for(int gi=0;gi<n;gi++){
if(gi!=ri && (green[gi]>0 || !hasGreen)){
for(int bi=0;bi<n;bi++){
if(bi!=gi && bi!=ri && (blue[bi]>0 || !hasBlue)){
int sum=0;
for(int i=0;i<n;i++){
if(i==ri)
sum+=green[i]+blue[i];
else if(i==gi)
sum+=red[i]+blue[i];
else if(i==bi)
sum+=red[i]+green[i];
else{
sum+=Math.min(Math.min(red[i]+green[i], green[i]+blue[i]), red[i]+blue[i]);
}
}
if(sum<minSum)
minSum=sum;
}
}
}
}
}
}
}
return minSum;
}

■Editorial
同じ解き方。
DP使った解き方も書いてあった。

■反省
・問題文の読み飛ばしで、各箱に最低ひとつずつボールが入っていることに気づかず、無駄なコードが入った。今回はそれでも正しくかけたから良かったが、注意しよう。
・ちなみに、最初はGreedyに、赤、青、緑の最低受け入れ口を見つけてしまい、その後積算すればいいと思ってしまったのだがサンプル入力でエラーが出たおかげで間違いに気づいた。・最大の入力条件であるn=50を試さずsubmitしてしまった。たかだがO(n^4)のアルゴリズムなので大丈夫と思ったが、本番ならば絶対にこれはしてはいけない。
・DPでの解き方も練習しておきたい。

■筋トレ記録
勝率75% (3/4)

2013年6月8日土曜日

筋トレ3日目: SRM569 Div1Easy "The Device"

■時間
18:45〜19:00
(昨日は早く出社しないといけなかったので出来なかった)

■問題 SRM569 Div1 Easy "The Device"
2つの固定長のビット列を入力に、同じ長さのビット列を一つ返す機械を持っている。
各ビットは入力ビットのAND, OR, XORのどれか。
(例えば3ビットの入力を受け付ける機械で、[XOR,OR,OR]という仕様ならば、入力ビット列110,010に対して110を返す)
何個かのビット列を持っており、それらの全組み合わせを入力させることで、機械の仕様を調べたい。
既にN個のビット列が与えられている時、あと何個あればどんな機械に対しても仕様を必ず確定できるか?

■試した解き方
あるビット位置のオペレーションを認識するために必要なビットの組み合わせを考える。
地道に、XORとANDを分けるために必要なビットの組み合わせ、XORとOR、ORとAND、のテーブルを作ると:
ANDとOR

 →(0,1)か(1,0)
ANDとXOR
 →(0,1)か(1,0)か(1,1)
ORとXOR
 →(1,1)
つまり、(0,1)か(1,0)と、(1,0)の組み合わせが作れれば、どの3種のオペレーションを分けられる。
これは、各ビット位置には0が1個以上、1が2個以上存在することが必要十分条件。
なので入力されたビット列から、このビット位置で既に存在する0と1の数を数え、足りてない数だけ余計にビット列が必要になることになる。
各ビット位置でこれを計算した最大値を計算すればいい。

■Editorial
同じ解き方。

■反省
一瞬問題を誤読(オペレーションがbit-wiseではなくビット列全てに適用されると誤解)していたため戸惑った。
また慎重になりすぎて時間食った。
これテーブル書かずに解くにはどうすればいいんだろう?

■筋トレ記録
勝率66% (2/3)

2013年6月6日木曜日

筋トレ2日目: SRM570 Div1Easy "Robot Herb"

■時間
15分ぐらい

■問題 SRM570 Div1 Easy "Robot Herb"
決められたプログラムを繰り返し実行して移動するロボットの最終位置を求めろ

■試した解き方
パラメータの範囲からして、真面目にシミュレーションすると莫大な時間がかかってしまうことは明らか。
一回プログラム実行したあとの向きは(前回の向き+定数)%4なので、4回やれば必ず元の向きになる
(もっというと、一回の実行後に元の向きにならない場合は、4回実行後必ず原点に戻ってる。今回は使わないけど)
なので、4回実行した場合の位置のズレを普通にシミュレーションし、全体の実行回数 div4回掛け算し、残りの数回分(T%4)はシミュレーションすれば求まる。
実行時間はO(a.length)

接続の問題でsubmitとシステムテストできてないが、多分大丈夫。

■Editorial
同じ解き方だった。

■反省
とても簡単だった。
これぐらい簡単な問題はもっと早くsubmitできないといけんね。

■筋トレ記録
勝率50% (1/2)







2013年6月5日水曜日

筋トレ1日目:SRM573 Div1 Easy

■時間スケジュール
7:11 開始
7:47 submit →system test fail
8:20 時間切れ
8:40 反省会終わり

■問題 SRM573 Easy 「Team Contest
3*N人の「強さ」を渡されて、3人組をN個作りたい。
組の強さが「組内の最強+最弱」で定義される時、自分の組よりも強いチームの数の最大値はなにか。

■試した解き方
感覚的にGreedyで解ける気もしたが、最強と最弱を組み合わせないほうがいい場合がある気がして、アホなO(2^n)な解法でsubmitしてしまった。
(N=16(最大のケース)で試しても2秒以内に終了してたからいいのかな、と思ってしまった)
結果:
submit1: 時間がどうとか以前に単純なバグでsystem fail
submit2: Execution Time Limit Exceeded
で撃沈

■解法
Editorialでもあまり満足に証明していないが、
1. 「最強の人と最弱の人の和が自分よりも強い場合は2番目に弱い人と3人で組み合わせる」
2. 「そうでない場合は、最弱の3人を組み合わせる」
の繰り返しで解ける。
証明は、そのうち補填。
<証明>

</証明>

■反省
・感覚でGreedyで解ける場合はやってしまったほうがいいのだろうか?SRMのプレッシャーの中で上記解法の有効性を証明出来る気がしない。
→実際のSRMの時は、他の人の動向(Submitまでの時間)を見て、早い人が多ければGreedyの回答をsubmitする、みたいな卑怯技も使える?
Div1の問題でBrute Forceできる問題なんて存在しないことを肝に免じる。何個か例題で上手く行っても、かならず本番は時間切れする。せめてDPの使いどころを見つけろ。
・最初のsubmitでイージーミスを犯したのは論外。
・解法を読んでなかった場合、解法の(1)はやっていても(2)に気づかず、へんなバグを起こしそう。

■筋トレ記録
勝率 0% 0/1

筋トレしよう:スランプから抜け出せ!

狙い
Topcoderで謎のスランプが訪れた:
だださがり

今年の2月にTopcoderのSRMを始めて以降、単調増加していたレーティングが下がる一方になった。
原因は、これまで余裕で解けていたDiv1-Easyの問題が何故か解けなくなったこと。
そんなわけで、毎日1問Easyの問題を解く、Topcoder筋トレを開始します。

■筋トレスケジュール
6時 起きる
6時5分 Easy開始
7時5分 Easy終了(もしくは我慢できなくてSystem Testを走らせるまで)
そこから20分ほど Editorial見ながら反省

■マイルストーン
6/14 →次のSRM(582)でEasy解く
7月中 →Easy解く率80%ぐらいキープ
8月中 →黄色目指す