AtCoder Beginner Contest 113
さっさと3完したけどDの解法が生えずに終了。
だいたい解法がDPの時はいつもこんな感じなので現状では良しとしよう。
A - Discount Fare
X + Y/2をする。
B - Palace
一個ずつ比較していって平均との差が最も小さいindexおよびその差を記録しておく。
本番では誤差の影響出ないだろうとdouble型使ったけど1000倍してintで解いた方が賢いね。
C - ID
特に考えることはなく実装あるのみ。 vector< pair< int, int > >を用いてP[i]とY[i]を格納した後、県の番号の昇順、県の番号が同じなら年代の若い順に並び替える。ソートする関数はこんな感じかな。
typedef pair<ll, ll> lpair; bool comp(lpair p1, lpair p2){ if(p1.first == p2.first){ return (p1.second < p2.second); } return (p1.first < p2.first); } vector<lpair> lp; sort(lp.begin(), lp.end(), comp);
最後の出力のために、もともと何番目にあったかを保持しておく必要がある。方法としてはmap使う or pairの中に何番目だったかの変数をもう一個追加するのが良さそう。圧倒的にmapの方がラク。
D - Number of Amidakuji
解説みたらすんなり理解できたのはよかった。ただ本番中には思いつかないと思った。
「今上から何段目、左から何列目にいて、そこまでにあり得る場合の数」を考えるとそのさきの経路はその値を元に計算できる、つまり部分問題として捉えることができる。
この場合の数がdp[i][j](上からi段目, 左からj列目)で、これを上から下に進めていくことで求める値 dp[H][K]が出てくる。dpの計算は前準備さえすればループ回してやるだけなので省略。
この問題の肝となるのが「i段目からi+1段目に移動するときのその段の線の書き方は何通りあるか」ということである。
このパターンは段によらず一定なので「各段において、列jから列kに移ることができる線の書き方は何通りあるか」というものを求めてあげれば良い。
二次元配列で持つとすればi => jの移動の総数はnum[i][j]となる。
W-1本の線が存在するかどうかはW-1ビットの数値で持ってあげるとよい。隣り合うビットが1になるものを排除するにはどうしたらいいかなーと考えていたところ、TLで「i & (i <<1)がtrueなら排除すればいい」というものを見つけた。頭良すぎでは。
あとはダメなものを排除した全パターンにおいてi => jの移動が可能なnum[i][j]をインクリメントしていけば良い。
もう少し詳しく言えば、それぞれのパターンにおいて縦線につく横線が0本ならnum[i][i] += 1をし、横線があれば、その横線によって移動する先をjとしたときnum[i][j]+=1, num[j][i] += 1をしてあげれば良い。
一番端の縦線のみ少し条件が違うので注意する。
W = 8の時(i,j)要素がこんな感じになれば正しく計算できてると思われる。
21 13 0 0 0 0 0 0 13 13 8 0 0 0 0 0 0 8 16 10 0 0 0 0 0 0 10 15 9 0 0 0 0 0 0 9 15 10 0 0 0 0 0 0 10 16 8 0 0 0 0 0 0 8 13 13 0 0 0 0 0 0 13 21
当然、2個以上離れた要素では0になっていることがわかる。
そろそろDPに慣れていかないとなぁ(毎回言ってる気がする)
C++でnCkやnPkを全列挙する関数
n!通りの順列を全列挙する関数はnext_permutationという備え付けの関数を使えばできたが、next_combinationなるものはどうやら存在しないようだった。
例えば「N個の要素からK個選んだ時の和」などを考えるとき、combinationのパターンを全列挙したくなる時が出てくる。
きっかけになったのが以下の問題。この問題だとNとKが固定されているのでこんな大げさに考えなくてもいいのだが、知っておいて損はないだろうと思った。
beta.atcoder.jp
関数の実装方法について考察している記事がいくつかあったので自分で一から作ろうかとも思ったが、実装がわかりやすく直接貼り付けていい感じに動くものが見つかったので今回はそれを使うことにした。時間があったらじっくり考えます。
参考にしたのはこのページ。
stackoverflow.com
以下がサンプルコード。NとKの値を入力するとN個からK個選ぶ組み合わせを全列挙する。
#include <bits/stdc++.h> #define rep(i, m, n) for(int i = m; i < (n); i++) #define print(x) cout << (x) << endl; #define printa(x,n) for(int i = 0; i < n; i++){ cout << (x[i]) << " ";} cout << endl; #define printa2(x,m,n) for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ cout << x[i][j] << " ";} cout << endl;} #define printp(x,n) for(int i = 0; i < n; i++){ cout << "(" << x[i].first << ", " << x[i].second << ") "; } cout << endl; #define INF (1e18) using namespace std; typedef long long ll; const ll MOD = 1e9 + 7; typedef struct{ int x; int y; } P; typedef struct{ ll to; ll cost; } edge; typedef pair<ll, ll> lpair; template <typename Iterator> inline bool next_combination(const Iterator first, Iterator k, const Iterator last) { /* Credits: Thomas Draper */ if ((first == last) || (first == k) || (last == k)) return false; Iterator itr1 = first; Iterator itr2 = last; ++itr1; if (last == itr1) return false; itr1 = last; --itr1; itr1 = k; --itr2; while (first != itr1) { if (*--itr1 < *itr2) { Iterator j = k; while (!(*itr1 < *j)) ++j; iter_swap(itr1,j); ++itr1; ++j; itr2 = k; rotate(itr1,j,last); while (last != j) { ++j; ++itr2; } rotate(k,itr2,last); return true; } } rotate(first,k,last); return false; } int main(){ cin.tie(0); ios::sync_with_stdio(false); ll N,K; cin >> N >> K; vector<ll> v(N); iota(v.begin(), v.end(), 0); do{ printa(v,K); }while(next_combination(v.begin(),v.begin() + K, v.end())); }
next_combination関数がメインとなる部分。この関数を通した後、先頭K要素を取得することで今回求める組み合わせを全て出力することができる。
なお、iotaは指定した数値から始まる数列を生成する関数。原始関数のι(イオタ)が由来だとか。
この関数を用いるとN!の全列挙ではTLEするようなケースでも組み合わせを求めることができる。
試しにいくつかの(N,K)でやってみたところ、(20,10)や(100,3)程度なら高速に動作した。
オーダーがどれくらいか見積もることができなかったけど、Nがそれほど大きくなければくらいで動いている気がする。
誰かちゃんとしたオーダーがわかる人がいたら教えてくだされ。
追記:
next_combinationで生成された、先頭からK個の数列がCombinationということは、その部分数列(ソート済み)をnext_permutationにかけてあげればpermutationの全列挙も可能だということに気づいた。
先ほどのコードのwhile文のところを
vector<ll> v2(K); do{ rep(i,0,K){ v2[i] = v[i]; } do { printa(v2,K); }while(next_permutation(v2.begin(), v2.end())); }while(next_combination(v.begin(),v.begin() + K, v.end()));
みたいにすればv2にはnPkの全列挙が入るね。やったね。
計算時間はK!倍になるのかな、いや律速はnext_combinationだからそこまで変わらないのかな。よくわからず。
なんか全体的にもっと速い方法がある気がするなぁ。突き詰めてくと面白そう。
MacでDockの日本語表示が文字化けしてしまった
ふと自分のMacのDockerのtooltipをみたらこんな感じになってました。
文字化けですね。何やらunicodeっぽい何かが出てきてしまっています。
原因は突き止められていないんですが、変わったことといえば自分のポケットWifiを自分のMacBookに接続したことくらいなので、多分その時に自動的にインストールされたドライバとかが原因ではないかと思われます。
とりあえず、ググって上の方に出てきた
$ killall Dock
こいつをターミナルで実行してみたが解決せず。
そのあと別のページにあった「/Library/Preferences/com.apple.dock.plistを消してDockを再起動」を実行したら直りました。つまりこんな感じですね。
$ rm /Library/Preferences/com.apple.dock.plist $ killall Dock
これ叩いて念の為Macを再起動してあげればうまくいくと思います。
Speed Wifi nextのドライバには気をつけましょう...
C++の約数とか使う時用の関数
なのでN=1e10とかなら動くはず。
vector<ll> divisor(ll M){ //約数の全列挙 vector<ll> dd; for(ll i = 1; i*i <= M; i++){ if(M % i == 0){ dd.push_back(i); if(i * i != M){ dd.push_back(M/i); } } } sort(dd.begin(), dd.end()); return dd; } vector<ll> factor(ll M){ //素因数分解 vector<ll> dd; if(M == 1){ dd.push_back(1); return dd; } for(ll i = 2; i*i <= M; i++){ while(M % i == 0){ dd.push_back(i); M /= i; } } if(M != 1) dd.push_back(M); sort(dd.begin(), dd.end()); return dd; }
よくある処理を使いやすい関数にまとめていく作業って大事。
Pythonでsubprocessを用いてshellコマンドを実行する
PythonからJavaのプログラムを直接呼び出したくなる場面があったので調べたところsubprocessという標準ライブラリを用いるとできるらしい。
import subprocess if __name__ == '__main__': result = subprocess.Popen("java -cp ./path/to file_1".strip().split(), stderr=subprocess.PIPE, stdout=subprocess.PIPE) if not result.stderr.read(): print(result.stdout.read())
このように実行すると別プロセスでシェルコマンドが実行されるがpythonは終了まで結果を待機する同期的な実行となる。subprocess.PIPEを用いることで標準出力や標準エラー出力をパイプを用いてresult変数に格納することが可能となる。
上の例ではエラーを吐かずに正常終了した場合のみ標準出力の結果を出力するような処理になっている。
Code Festival 2018 qual B
ABC3完の91位。500点のCを20分程度で一発で通せたのはなかなか大きかった。
A
100 -(Nの倍数な数字の個数)
B
一番顔が面白いやつにXを全て足すと最適
C
制限の201800をみて、1000 * 1000/201800をしたらほぼ5だったので、よっぽどのことがない限りは一つのXで5個分をカバーしないとわかった。効率よく埋めようと考えて十字形のパーツを隙間なく入れていくことを考えた時に一番はじめに桂馬とび型のパターンが浮かんだのが幸いだった。
桂馬じゃない形で埋まりそうなのもあったっぽいがそれだと250000個くらい必要でWAするらしい。こっち先に思いつかなくてよかった...
WAペナがなかったので1000 * 1000でもXの個数確かめずに出したのが結果的に時間の短縮になってよかったかも。
D
なんか確率の問題やばそうって思ってどちらかというととっつきやすそうなEに移動した
E
1/最小公倍数に持っていくのは良いとして、±LCM/nを使って1を作れればいいなーと思ってたけどなんせ数が大きすぎるし320回でおさまる気もしなかった。MODでどうにかするんだろうなーという発想は浮かんだがそこから考察を進める力はなかった
100位以内に入ったからThanksの方はもしかしたら参加できるのかな?こんな運がよかった早解きで参加していいものなのかどうか...
AtCoder水色になりました
無事水色になることができた。
ぶっちゃけると水色ならもうちょっとすぐになれると思っていた...笑
水色を目前に控えた時にC問題にどハマりして死ぬみたいな展開を何回かやってしまったのでそれでしばらく停滞したんだと思う。
あと、AGC早解きは貴重なレートアップの機会だった()
別にここまでは特にストイックに何かをやったということはなく、過去のC問題とD問題を新しいものから順にやっていってるくらい。
C問題はほとんど解けるけどD問題でヒントなしで行けるのはまだ2~3割程度か。
500点とかになった瞬間手も足も出なくなるケースが多いけどそこは慣れていくしかなさそう。
とりあえず今後レート上げてくためにしばらくはCD早解きになると思うので、まずは最低限400点のD問題をささっと解けるレベルにしていきたいと思ってる。
あと、これからARCになるとNo sub撤退したくなる機会が増えてくる気もするけど、出来るだけしないように心がけよう。
多分レート上がるに従ってレートの減少を怖がり始めるだろうけどそこ気にしてたら肝心の精進の妨げになると思われるので。
これからもしばらくはCD問題の過去問を潰していくつもり。一通り潰した上でこっから伸ばすには何が必要なのかとか考えていこうかな。とりあえず今の所全く要領がつかめないDPをどうにかしないといけない。