No.458 異なる素数の和

Quiz

https://yukicoder.me/problems/no/458

Submit

https://yukicoder.me/submissions/339334

解法

  • dp[i] : i を異なる素数の和で表した時の最大の和の回数
  • 答えはdp[N]

考察1 dp[i]をより小さいdpで埋めていく

f:id:peroon:20190417123334j:plain

  • よくあるDP
  • しかし今回はダメ
    • dp[7] != dp[2] + dp[5] であるため
    • dp[2] = 1 (素数2のみ)
    • dp[5] = 2 (素数2と3)
    • dp[7] = 2 (素数2と5)
    • dp[7] = 3ではない (素数が2, 2, 3となるため)

考察2 dp[i]をiが小さい方から埋めていく (素数を滑らす)

FOR(i, 0, N+1){
  for(int p : prime_list){
  }
}
  • この形
  • しかしどう埋めていけばいいか不明

f:id:peroon:20190417123357j:plain

考察3 素数を滑らせながらdp[i]を埋めていく

for(int p : prime_list){
  FOR(i, 0, N+1){
  }
}
  • この形
  • しかし画像のように、順方向にiを動かしてはいけない。同じ素数を複数回使ってしまうから。

f:id:peroon:20190417123500j:plain

  • なので、正しくは下記のループで埋めていけばいい
for(int p : prime_list){
  for(int i=N; i>=0; i--){
  }
}

No.105 arcの六角ボルト

f:id:peroon:20190417091935j:plain

Quiz

https://yukicoder.me/problems/no/105

Submit

https://yukicoder.me/submissions/339320

解法

  • 回転が50度未満なので、(1, 0)の回転後の点はyが正のもののうちxが一番右
  • その1点だけだけから角度を求める
  • atan2(y, x)を使う

注意点

  • 与えられるデータには誤差があるので、
    • (0.999, 0.00000000001)が誤差により
    • (0.999, -0.00000000001)になったりすると、頼りにしている1点を見逃してしまう
  • yに少し値を足して、誤差で負にならないようにしておく

C - ABCAC 〜はじめてのZ Algorithm〜

Quiz

https://atcoder.jp/contests/arc055/tasks/arc055_c

Submit

https://atcoder.jp/contests/arc055/submissions/4992693

解法

  • editorialの通り

ACまでに踏んだステップ

  • 部分点 20点を狙う
    • |S| > 2000 のときはすぐ終了してテストをすぐ終わらせる
    • a, c の探索もナイーブに実装する
    • 部分点が通るまでやる
  • 通ったら、高速化
    • ローリングハッシュの二分探索?よくわからん
    • Z Algorithm いけそう
      • sにZ Algorithmをして配列を持っておく (a用)
      • sを反転した文字列t
      • tにZ Algorithmをして配列を持っておく (c用)
      • AC

学び

  • 部分点から満点までの距離は意外と近い
  • 部分点と満点では解法が違うことがあるが、部分的な入れ替えで済むことが多く、無駄にはならない
    • 大学受験の数学に似ている
  • Z algorithmは導入しやすい

ローリングハッシュにより文字列検索をO(m*n) => O(m+n)に高速化

参考

  • 蟻本第二版 p332

プログラミングコンテストチャレンジブック [第2版] ?問題解決のアルゴリズム活用力とコーディングテクニックを鍛える?

プログラミングコンテストチャレンジブック [第2版] ?問題解決のアルゴリズム活用力とコーディングテクニックを鍛える?

用途

  • 文字列検索の高速化

ナイーブな文字列検索 O(m*n)

  • 普通に文字列検索を実装すると、
    • 文字列sから文字列tを探すとして
    • sの長さsl
    • tの長さtl
    • sのindex=0から滑らせてtの長さ分一致をチェックするので、計算量はO(sl * tl)
  • ナイーブな実装↓
// s内のtを検索
vector<ll> naive_search(string s, string t){
    vector<ll> index_list;
    FOR(i, 0, s.size() - t.size() + 1){
        
        bool is_same = true;
        FOR(j, 0, t.size()){
            if(s[i+j]!=t[j]){
                is_same = false;
                break;
            }
        }
        if(is_same){
            index_list.push_back(i);
        }
    }
    return index_list;
}

ローリングハッシュを用いた文字列検索 O(m+n)

  • 互いに素な定数 1<b<h を用意する
  • 文字列 t のハッシュ値を以下で定義する

f:id:peroon:20190415125613j:plain

  • sの先頭からtl分の部分文字列のハッシュを求める
    • 部分文字列を s[1...tl]と書くとする
  • tのハッシュを求める
  • ハッシュが一致すれば文字列も一致している
    • (ハッシュは衝突していないとする)
  • 次はsの2文字目からtl分を使ってハッシュを求めたいが、これは先程求めたsの部分文字列のハッシュに少し足し引きすれば求まる
    • O(1)
    • 下記画像参考

f:id:peroon:20190415130547j:plain

  • 詳しくは蟻本(第二版 p332)を見てほしい。hを264とし、型をunsigned long longとすることでmodを省略している

検証コード

// s内のtを検索
vector<ll> naive_search(string s, string t){
    vector<ll> index_list;
    FOR(i, 0, s.size() - t.size() + 1){
        
        bool is_same = true;
        FOR(j, 0, t.size()){
            if(s[i+j]!=t[j]){
                is_same = false;
                break;
            }
        }
        if(is_same){
            index_list.push_back(i);
        }
    }
    return index_list;
}

vector<ll> rolling_hash_search(string s, string t){
    vector<ll> index_list;
    ll SL = s.size();
    ll TL = t.size();

    // b, hは互いに素 (gcd == 1)
    ll b = 1e6 + 7;
    ll h = 1e9 + 7;

    // 蟻本でいう、bのm乗
    ll a = 1;
    FOR(i, 0, TL){
        a *= b;
        a %= h;
    }
    
    // 先頭のローリングハッシュ
    ll s_hash = 0;
    FOR(i, 0, TL){
        s_hash = s_hash * b + s[i];
        s_hash %= h;    
    }

    ll t_hash = 0;
    FOR(i, 0, TL){
        t_hash = t_hash * b + t[i];
        t_hash %= h;    
    }

    // sの始点を1つずつ進めながらハッシュ値をチェック
    FOR(i, 0, SL - TL + 1){
        if(s_hash==t_hash){
            index_list.push_back(i);
        }

        if(i+TL<SL){
            s_hash = s_hash * b - s[i] * a + s[i+TL];
            s_hash %= h;
        }
    }

    return index_list;
}

int main(){
    string s = "unvhusmjlvieloveuybouqvnqjygutqlovedkfsdfgheaiuloveaeiuvaygayfg";
    string t = "love";

    auto A = naive_search(s, t);
    vprint(A);

    auto B = rolling_hash_search(s, t);
    vprint(B);
    
    return 0;
}

出力

12 31 47 
12 31 47

注意点

衝突

ll b = 1e7 + 7;
ll h = 1e11 + 7;
  • これも素数っぽくていいかも

h=264

Macで#include<bits/stdc++.h>を導入

環境

目的

  • include<bits/stdc++.h>ができるようにする
  • コードを短くするため

設定

動機

  • includeという本質じゃない部分がコードの大きな部分を占めていたので減らしたい

欠点

  • 上記設定をしていないユーザは私のコードをそのままコピペしても動かない