dein.vimとdeopleteをmacにインストールする。
dein.vim
dein.vimは暗黒のプラグインマネージャです。
元々neobundleを使っていましたが、後継が1.5年前に出ているのでさすがに乗り換えました。
インストール
githubのQuick start、dein.vimのインストール自体にハマってしまったメモ - Qiitaを参照。
1、Run below script. $ curl https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.sh > installer.sh $ sh ./installer.sh {specify the installation directory}
これを実行すると、最後に「今表示したscriptをvimrcに貼り付けてね(意訳)」と表示されるので見逃さずに貼り付けましょう(1敗)。
" If you want to install not installed plugins on startup. if dein#check_install() call dein#install() endif
この部分のコメントアウトを外して起動時にインストールするようにします。
You can specify revision/branch/tag. call dein#add('Shougo/deol.nvim', { 'rev': 'a1b5108fd' })
vim8だとこの部分で deol.nvim requires Neovim と言われます。
, { 'rev': 'a1b5108fd' } の部分を消して、deol.nvimを削除、その後インストールすれば使えます。
deoplete.nvim
自動補完プラグイン。
インストール
vimrcに以下を記述。
C++の補完のため、deoplate-clangもインストールする。
GitHub - deoplete-plugins/deoplete-clang: deoplete.nvim source for C/C++/Obj-C/Obj-C++ with clang-python3
brew install cmake brew install llvm --with-clang
必要なものを揃える。
call dein#add('Shougo/deoplete.nvim') call dein#add('zchee/deoplete-clang') if !has('nvim') call dein#add('roxma/nvim-yarp') call dein#add('roxma/vim-hug-neovim-rpc') endif
上記をvimrcに追加。
g:deoplete#sources#clang#libclang_path g:deoplete#sources#clang#clang_header
この二つの設定は必須です。
libclang_pathにはlibclang.dylibへのpath、clang_headerはclangディレクトリへのpathです。
libclang_pathと同じディレクトリにclangのディレクトリがあれば、それへのpathをclang_headerにします。
denite.vimはうまく動かないので諦めました。
AtCoder Beginner Contest D問題ジャンル分け
imos法
DP・メモ化再帰
未分類
D - マーブル
別解:最小費用流
D - トランプ挿入ソート
D - 金塊ゲーム
D - 高橋くんの苦悩
D - サプリメント
D - 25個の整数
D - ナップサック問題
D - 経路
D - No Need
D - Mixing Experiment
別解:半分全列挙
桁DP
TreeDP
BitDP
行列累乗
2次元累積和
フロー
グラフ
D - バスと避けられない運命
D - 閉路
D - 高橋くんと木の直径
D - トレジャーハント
D - Candidates of No Shortest Paths
D - Score Attack
D - Transit Tree Path
D - joisino's travel
D - Restoring Road Network
Union-find
ダブリング
D - 阿弥陀
別解:巡回置換
セグメントツリー | Binary Indexed Tree
シミュレーション
ARC016 - C ソーシャルゲーム
問題概要
商品が n 種類、くじが m 種類あります。
くじを一度引くのにお金がかかります。
くじからは、ある確率である商品が手に入ります。
最善を尽くしたとき、全ての商品を集める金額の期待値を求めてください。
考えたこと
期待値についてはこちら 期待値と条件付確率 - math314のブログ、
問題文についてはこちら ARC016-C「ソーシャルゲーム」 の記事が非常に参考になります。
こちら 期待値と条件付確率 - math314のブログ の記事で例として挙げられているコイントスの期待値についてです。
頂点の値が、連続で表が出た回数を示しています。
青矢印が表が出たことを、橙矢印が裏が出たことを表しています。
期待値はこのように状態の遷移から式を立てることができます。
単純なくじで、期待値を求める式を立ててみます。
E は、その状態から全ての商品を集めるためにくじを引く回数を表しています。
頂点の値は、商品の入手を bit で表しています。
00 の状態からそれぞれ 10, 01 の状態へ遷移し、最後は 11 となります。
11 は全て揃った状態でありこれ以上はくじを引く必要がないため E_11 = 0 です。
10 の状態から 11 になるに 1 番目の商品を引かなければなりません。
このとき、 5% の確率で引けるので、くじを引く回数の期待値は 100 / 5 回です。
もう片方は 95% なので、 100 / 95 回です。
00 からは 5% の確率で 01 になり、 01 からは 100 / 95 回で全て揃います。
また、 95% の確率で 10 になり、 10 からは 100 / 5 回で全て揃います。
これで、 00 から 11 までの期待値が計算できました。
式を実際に立ててみると、後ろから計算することで解がわかることに気付けます。
あとは式を計算できるように実装します。
実装
sum が自分自身に返ってこない場合の期待値を表しています。
100 / (100 - k) が自分自身に返ってくる確率の逆数(期待値)を表しています。
手計算した式をどうにか実装したらうまくいった、という感じです。
#include<bits/stdc++.h> #define range(i,a,b) for(int i = (a); i < (b); i++) #define rep(i,b) for(int i = 0; i < (b); i++) #define all(a) (a).begin(), (a).end() #define show(x) cerr << #x << " = " << (x) << endl; const int INF = 1e8; using namespace std; //i番目のビットを返す bool getBit(int num, int i){ return ((num & (1 << i)) != 0); } //i番目を1にする int setBit(int num, int i){ return num | (1 << i); } int main(){ int n, m; cin >> n >> m; vector<int> c(m), cost(m); vector<vector<pair<int, int>>> kuji(m); //番号、確率 rep(i,m){ cin >> c[i] >> cost[i]; rep(j,c[i]){ int a, b; cin >> a >> b; a--; kuji[i].emplace_back(a,b); } } double dp[1 << 10]; rep(i,1 << 10) dp[i] = INF; dp[(1 << n) - 1] = 0; for(int s = (1 << n) - 1; s >= 0; s--){ rep(i,m){ double sum = cost[i], k = 0; for(auto j : kuji[i]){ if(not getBit(s, j.first)){ sum += dp[setBit(s, j.first)] * j.second / 100.0; }else{ k += j.second; } } dp[s] = min(dp[s], sum * 100.0 / (100 - k)); } } cout << fixed << setprecision(10) << dp[0] << endl; }
ARC067 - E Grouping
問題概要
N 人をグループ分けします。
一つのグループにつき A ~ B 人で構成し、
同じ人数のグループは 0 か C ~ D 個作らなければいけません。
このようなグループ分けは何通りありますか。
DPの解説
DPテーブルを書いてみます。
横軸が、グループ分けしていない、残っている人の数。
縦軸が、i 人以下のグループのみでグループ分けしていることを表します。
dp[i][j] = i 人以下のグループのみにでグループ分けした時、グループ分けされていない人の数が j 人の時の通り数
入力は、3 1 2 1 2 だとします。
まず、0 人以下のグループのみでグループ分けした場合を考えます。
「誰も選べない」の 1 通りしかないので、dp[0][n] = 1。
残りの人数を減らせないので、その他はすべて0。
次に、1 人のグループのみでグループ分けした場合を考えます。
残り 3 人いるときに、1 人グループを 1 個作った場合、3 人のうちから 1 人選ぶので、 。
残り 3 人いるときに、1 人グループを 2 個作った場合、3 人のうちから 1 人選んで、 2 人のうちから 1 人選ぶ。
そして、重複を消せばよいので、 。
同じグループは 3 個以上作れないので、次に進みます。
残り2 人いるときに、1 人グループを 1 個作った場合……ですが、
dp[0][2] = 0 のため、dp[1][1] += となります。
計算をしても +0 するだけなので省略。
これで 1 人グループを作る場合をすべて試せました。
この計算結果は、1 人グループのみでグループ分けした結果の通り数を表しています。
縦軸は、i 人以下のグループのみでグループ分けしていることを表すので、0 行の結果を加える必要があります。
次に、2 人以下のグループのみでグループ分けした場合を考えます。
残り 3 人いるときに、2 人グループを 1 個作った場合、 。
2 人グループは 1個以上作れないので、次に進みます。
残り 2 人いるときに、2 人グループを 1 個作った場合、 。
2人グループを作る場合が終わりました。
あとは先と同様に、1行の結果を加えます。
これで本当に正しいのか、組み合わせを列挙してみます。
残り 3 人のとき
{} (空集合)
より 1 通り。
残り 2 人のとき
{1}
{2}
{3}
より 3 通り。
残り 1 人のとき
{1, 2}
{1, 3}
{2, 3}
{ {1}, {2} }
{ {1}, {3} }
{ {2}, {3} }
より 6 通り。
残り 0 人のとき
{ {1, 2}, {3} }
{ {1, 3}, {2} }
{ {2, 3}, {1} }
より 3 通り。
どうやら正しいようです。
実装
先に組み合わせ、階乗の逆元を計算しておきます。
あとは遷移させます。
計算結果がどんどん加算されていくDPなので、更新する方向に気をつければ 1 次元配列で計算できます。
残り 1000 人を 1 人グループに分ける場合、
1 個のとき:
2 個のとき:
3 個のとき:
....
と組み合わせの計算に時間がかかるので、前回の計算結果を用いて計算量を落としています。
#include<bits/stdc++.h> #define range(i,a,b) for(int i = (a); i < (b); i++) #define rep(i,b) for(int i = 0; i < (b); i++) #define all(a) (a).begin(), (a).end() #define show(x) cerr << #x << " = " << (x) << endl; //const int INF = 1e8; using namespace std; const long long M = 1000000007; long long nCr[1005][1005]; void Pascals(){ nCr[0][0] = 1; range(i,1,1001){ rep(j,i + 1){ if(j == 0) nCr[i][j] = nCr[i - 1][j]; else if(j == i) nCr[i][j] = nCr[i - 1][j - 1]; else nCr[i][j] = (nCr[i - 1][j] + nCr[i - 1][j - 1]); nCr[i][j] %= M; } } } //べき乗 x^n mod M long long power(long long x, long long n){ long long res = 1; if(n > 0){ res = power(x, n / 2); if(n % 2 == 0) res = (res * res) % M; else res = (((res * res) % M) * x ) % M; } return res; } //階乗 long long fact[1005]; long long factorial(long long n, long long r){ long long res = 1; range(i,r,n + 1){ res*= i; res%= M; fact[i] = res; } return res; } int main(){ Pascals(); factorial(1000,1); long long fact_rev[1005]; rep(i,1001) fact_rev[i] = power(fact[i], M - 2); long long n, a, b, c, d; cin >> n >> a >> b >> c >> d; long long dp[1005] = {0}; dp[n] = 1; range(i,a,b + 1){ range(k,i * c,n + 1){ long long person = k; long long comb = 1; range(j,1,d + 1){ long long join = i * j; if(k - join < 0) break; (comb *= nCr[person][i]) %= M; person-=i; if(j >= c){ (dp[k - join] += dp[k] * comb % M * fact_rev[j] % M) %= M; } } } } cout << dp[0] << endl; }
ABC009 - D 漸化式
問題概要
二つの数列 と が入力で与えられる。
数列 A は以下の式によって計算できる。
数列 A の M 番目の値を求めよ。
解き方
漸化式を計算するには、DPを使えば良いです。しかし、制約が大きく、DPでは解けません。
別の方法を使って漸化式を解く必要があります。
そこで、行列累乗を使います(あり本 P.180参照)。
行列累乗に and や xor が使えるのかですが、それは atcoder の公式解説を参照してください。
あり本を見ながらやれば実装できます。ほとんどあり本のプログラムそのままです。
正しい順番で値を入れて、正しい値で初期化して、演算子を変えましょう。
ただ、割と引っかかるポイントが多かったです。
気をつけるところ
and は論理積
bit の and が取りたい時は、 bitand か & を使いましょう。
各項の順番
行列に値を入れる際、右から左に並べるのか、左から右に並べるのか(もちろん上下も)。
and 演算子の単位元
普通に掛け算をするのであれば、単位元は 1 です。
あり本にあるプログラムや式は、行列の掛け算を行うため、 単位元に 1 が使われています。
しかし、この問題では and 演算子を使うため、単位元は全 bit が 1 の値です。
初期化ミスに気をつけましょう。
m ≤ k の時の処理
m ≤ k の時は、与えらえれた入力をそのまま返せば良いです。
入力を行列累乗のために逆順に持っていたことを忘れるとWAです(2敗)。
実装
#define int long long なので、単位元は -1 を使っています。
#include<bits/stdc++.h> #define range(i,a,b) for(int i = (a); i < (b); i++) #define rep(i,b) for(int i = 0; i < (b); i++) #define all(a) (a).begin(), (a).end() #define show(x) cerr << #x << " = " << (x) << endl; //const int INF = 1e8; using namespace std; #define int long long typedef vector<vector<int>> mat; mat mul(mat &a, mat &b){ mat c(a.size(), vector<int>(b[0].size())); rep(i,a.size()){ rep(k,b.size()){ rep(j,b[0].size()){ c[i][j] = (c[i][j] xor (a[i][k] bitand b[k][j])); } } } return c; } mat pow(mat a, int n){ mat b(a.size(), vector<int>(a.size(),0)); rep(i,b.size()) b[i][i] = -1; while(n > 0){ if(n & 1) b = mul(b,a); a = mul(a, a); n >>= 1; } return b; } int solve(int n, mat A, vector<int> c){ mat a(A.size(), vector<int>(A.size(), 0)); for(int i = c.size() - 1; i >= 0; i--){ //a[0][i] = c[A.size() - 1 - i]; ここの順番が逆だった。 a[0][i] = c[i]; } rep(i,c.size() - 1){ a[i + 1][i] = -1; } a = pow(a,n); //行列Aのn乗。 mat res = mul(a, A); return res[0][0]; } signed main(){ int k, m; cin >> k >> m; mat a(k, vector<int>(1)); vector<int> c(k); rep(i,k) cin >> a[k - i - 1][0]; rep(i,k) cin >> c[i]; if(m <= k){ cout << a[k - m][0] << endl; }else{ cout << solve(m - k,a,c) << endl; //-kを忘れずに } }
ABC025D - 25個の整数
D - 25個の整数
解説を見て実装を放置していたため、考察要素はないです。
問題概要
5 * 5 のマスがあります。 1 ~ 25 までの数値を、各マスに一つずつ書きます。
ただし、以下の条件を満たすように書かなければいけません。
縦、あるいは横に 3 つ並んだどのマスの数値も、単調減少、あるいは単調増加してはいけない。
あらかじめいくつかのマスはすでに数値が書かれています。
条件に従い全ての全てのマスを埋めるとき、何通りの埋め方がありますか。
考えたこと
制約は N ≤ 20 だけど、2^25のdpテーブル持ちたい…
2^25のdpテーブルをギリギリ持てた。
マスに何も書かれていない状態から考える。
dfsで 1 から順番に書いていき、全部埋められたら 1 を返す。
また、メモ化して無駄な部分を減らす。
元々数値が書いてあるということは、数値が書ける場所が 1 箇所しかないと考えられる。
bit と 2 次元の盤面を行き来する必要がある。
(x, y)は、bit の (x + y * 5) 番目と考えればよい。
実装
check関数で書けるかどうかの判定をしています。
atcoder公式の解説にある通り、数値を書く時のマスの状況が 4 種類に分けられます。
1. 端のマスに書き込む。
2. 書き込むマスの両側がすでに書き込まれている。
3. 書き込むマスの片方がすでに書き込まれている。
4. 書き込むマスの両方が書き込まれていない。
そして、書き込めないパターンは 3 番のみです。
書き込むマスの前後の bit の xor を取り、反転させれば条件を満たすかを判定できます。
それを縦横両方確かめ、and を取れば、書き込めるかわかります。
1 から順番に埋めていくため、実際に値の大小を比べる必要はありません。
dfs関数では、 num を書き込むマスを全通り試しています。
書き込める場所は、 "入力で 0 が与えられている" かつ "集合 s に含まれていない" 場所です。
1 から順番に書き込んでいくとはいえ、元々書き込まれている部分に書き込むことはできません。
#include<bits/stdc++.h> #define range(i,a,b) for(int i = (a); i < (b); i++) #define rep(i,b) for(int i = 0; i < (b); i++) #define all(a) (a).begin(), (a).end() #define show(x) cerr << #x << " = " << (x) << endl; using namespace std; const int M = 1000000007; int dp[1 << 25]; vector<bool> used_number(25,0); vector<bool> vacant_space(25,0); pair<int, int> p[25]; bool getBit(int num, int i){ return ((num & (1 << i)) != 0); } int setBit(int num, int i){ return num | (1 << i); } bool check(pair<int, int> p, int s){ int y = p.first; int x = p.second; bool xf, yf; if(x == 0 || x == 4){ //端に置くケース xf = true; }else{ bool left_bit = getBit(s, (x - 1) + y * 5); bool right_bit = getBit(s, (x + 1) + y * 5); xf = not (left_bit xor right_bit); } if(y == 0 || y == 4){ yf = true; }else{ bool up_bit = getBit(s, x + (y - 1) * 5); bool down_bit = getBit(s, x + (y + 1) * 5); yf = not (up_bit xor down_bit); } return xf and yf; } int dfs(int s, int num){ //s = 埋められた場所の集合 if(dp[s] != -1) return dp[s]; if(used_number[num]){ //あらかじめ数字が置かれている場合 int y = p[num].first; int x = p[num].second; int next = setBit(s, x + y * 5); if(check(p[num],s)){ return dfs(next, num + 1); }else{ return dp[next] = 0; } } int res = 0; rep(i,25){ // num を置ける場所を探す if(not getBit(s,i) && vacant_space[i]){ if(check(make_pair(i / 5, i % 5), s)){ res += dfs(setBit(s,i), num + 1); res %= M; } } } return dp[s] = res; } signed main(){ memset(dp, -1, sizeof(dp)); dp[(1 << 25) - 1] = 1; //全部埋まったら1通り rep(i,5) rep(j,5){ int a; cin >> a; if(a == 0) vacant_space[i * 5 + j] = true; else used_number[a - 1] = true; p[a - 1] = make_pair(i,j); } cout << dfs(0,0) << endl; }
AOJ 0553 ダンジョンの解説
AOJ 0553 ダンジョン
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0553
考察
問題点
あり本にある優先度キューを用いたガソリン問題と似ています。
ただし、こちらの問題では、ガソリンを入れられる量の上限があります。
体力が0以下になる前に、どこかで回復しなければいけない
->どうせ回復するなら一番回復量が多い泉を使いたい
->体力が0以下になった時に、最も回復量が高い泉を使ったことにすれば良い
これは優先度キューを用いれば実現できそうです。
やっかいなのは体力の上限で、「泉の回復量は多いが、体力が減っていないため、実際の回復量が小さくなる」ということが起きるため、優先度キューだけを用いてもうまくいきません。
「泉の回復量が多い順」ではなく、「実際の回復量が多い順」に並べなければいけません。
実際の回復量は、体力と泉の回復量がわかれば求めることができそうです。
そこで、各階を訪れた際の体力を持っておき、それを元に「実際の回復量」を決めましょう。
実際の回復量を求める
体力の更新方法をまずは考えます。
地下 i 階で体力が0になったので、地下 j 階で泉を使ったことにする( i > j )。
すると、当然地下 j 階〜 i 階の体力は増えます。つまり、区間に対する加算を行う必要があります。
また、加算した際に、体力が上限を超えてはいけません。
つまり、地下 j 階で体力を回復する場合、地下 j 階〜 i 階の区間のすべての体力は、(体力の上限 - j 階の回復量)以下である必要があります。
裏を返せば、地下 j 階の回復量は、(体力の上限 - 地下 j 階〜 i 階の区間で最大の体力)以下になります。
よって、実際の回復量を求めるためには、区間の最大値を求める必要があることがわかります。
方針
使うもの
優先度キュー、 セグメントツリー(区間に対する加算、 区間の最小値を求める)
1)体力をセグメントツリーに記録する
2)泉の情報をキューに入れる
3)体力を消費する
4)体力が0以下になったとき
1)最も回復量が多いものを取り出す
2)区間[回復する場所、現在地]の最大値を求める
3)体力の最大値 + 回復量 <= 体力の上限 のとき、その泉を使えるだけ使う。
使えないとき、実際の回復量を更新してキューに戻す。その後、1に戻る
4)泉を使って回復したことにし、区間に加算する。
実装
#include<bits/stdc++.h> #define range(i,a,b) for(int i = (a); i < (b); i++) #define rep(i,b) for(int i = 0; i < (b); i++) #define all(a) (a).begin(), (a).end() #define show(x) cerr << #x << " = " << (x) << endl; //const int INF = 1e8; using namespace std; #define int long long const int MAX_N = 100010; class segTree{ public: int n, dat[4 * MAX_N]; void init(int n_, int value, int dat_b[4 * MAX_N] = NULL){ n = 1; while(n < n_) n *= 2; rep(i,2 * n){ dat[i] = value; if(dat_b != NULL) dat_b[i] = value; } } void output(int a[4 * MAX_N]){ show("print"); range(i,1,n * 2) cout << (a[i] == INT_MAX ? 0 : a[i]) << ' '; cout << endl; } }; class starrySky : public segTree{ private: int query(int a, int b, int k, int l, int r){ if(b <= l || r <= a) return 0; if(a <= l && r <= b) return dat[k] + dat_add[k]; int vl = query(a, b, k * 2, l, (l + r) / 2); int vr = query(a, b, k * 2 + 1, (l + r) / 2, r); return max(vl, vr) + dat_add[k]; } void add(int a, int b, int k, int l, int r, int x){ if(b <= l || r <= a) return; if(a <= l && r <= b){ dat_add[k] += x; }else{ add(a, b, k * 2, l, (l + r) / 2, x); add(a, b, k * 2 + 1, (l + r) / 2, r, x); dat[k] = max(dat[k * 2] + dat_add[k * 2], dat[k * 2 + 1] + dat_add[k * 2 + 1]); } } void init(int n_){ segTree::init(n_,0,dat_add); } public: int dat_add[4 * MAX_N]; starrySky(int n){ init(n); } int query(int a, int b){ return query(a, b, 1, 0, n); } void add(int s, int t, int x){ add(s, t, 1, 0, n, x); } void add(int i, int x){ add(i, i + 1, 1, 0, n, x); } }; signed main(){ long long n, h; cin >> n >> h; //1-indexのセグ木 starrySky seg(n); //体力を保持する priority_queue<pair<long long, int>> q; //泉の回復量、pos long long cur = h, ans = 0; rep(i,n - 1){ long long damage, heal; cin >> damage >> heal; q.push(make_pair(heal, i)); seg.add(i + 1, cur); cur -= damage; while(cur <= 0){ pair<long long, int> use = q.top(); q.pop(); long long maxHP = seg.query(use.second + 1, i + 2); //回復地点から現在地までの体力の最大 if(maxHP + use.first > h){ //回復量が上限を超えるので、実際の回復量に変更 q.push(make_pair(h - maxHP, use.second)); continue; } long long can_use = (h - maxHP) / use.first; long long require = ceil((-1.0 * cur + 1) / use.first); long long used = min(can_use, require); ans += used; cur += used * use.first; seg.add(use.second + 1, i + 2, used * use.first); q.push(use); } } cout << ans << endl; }