Page List

Search on the blog

ラベル cpp の投稿を表示しています。 すべての投稿を表示
ラベル cpp の投稿を表示しています。 すべての投稿を表示

2020年2月7日金曜日

c++17 で stg::lcm が追加されててハマった話

 Codeforcesのk-roundingという問題を解いたらWAが出た。

「nとkが与えられるので、末尾に0がk個以上ついてnで割れる最小の自然数を求めよ」という問題で、nと10^k の最小公倍数を求めれば良さそうなので、以下のようなコードをsubmit。

#include <bits/stdc++.h>
 
using namespace std;
 
#define REP(i,n) for(int i=0; i<(int)(n); i++)
#define FOR(i,b,e) for (int i=(int)(b); i<(int)(e); i++)
#define ALL(x) (x).begin(), (x).end()
 
const double PI = acos(-1);
 
long long gcd(long long a, long long b) {
  if (b == 0)
    return a;
  return gcd(b, a%b);    
}
 
long long lcm(long long a, long long b) {
  return a / gcd(a, b) * b;
}
 
int main() {
  ios_base::sync_with_stdio(0);
  cin.tie(0);
 
  int n, k;
  cin >> n >> k;
 
  int x = 1;
  REP (i, k) x *= 10;
 
  cout << lcm(n, x) << endl;
  
  return 0;
}

結果Wong Answer。
詳細なテストケースを見てみると、n = 123456789, k = 8 で 1566078208 と出力されている。試しにローカルで動かしてみると12345678900000000と正答を出力している。

上のコードでは main の中では int を使っているけど引数としてlcmに渡した時点でlong longになるのでオーバーフローはしないはずだが、試しにmainを以下のように変えてsubmitしてみると予想に反してAC。

int main() {
  ios_base::sync_with_stdio(0);
  cin.tie(0);
 
  long long n, k;
  cin >> n >> k;
 
  long long x = 1;
  REP (i, k) x *= 10;
 
  cout << lcm(n, x) << endl;
  
  return 0;
}

サーバでは g++17 で実行されるけどローカルだとg++17じゃないなと気づいて、もしやと思って調べると std に gcdlcm が追加されてました。引数を int で渡すとこちらが実行されていたようです。

とりあえずローカルでコンパイルするときに g++17 を使うようにしたものの、サンプルケースにオーバーフローするものがないと意図せず std::lcm の方が呼び出しされていることに気づけないので、自分のライブラリ関数の名前を変えた方がいい気がしています。


2016年3月22日火曜日

知ってると便利なSTL(13)tuple

make_tupleで変数をまとめてタプルにすることができる。
get<i>()でタプルのi番目の要素を取得できる。
tieでタプルの中身を展開して変数に代入できる。
これは便利。
#include <tuple>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
  auto t = make_tuple(1, 0.5, "hoge");

  cout << get<0>(t) << endl;
  cout << get<1>(t) << endl;
  cout << get<2>(t) << endl;

  int a;
  double b;
  string c;
  tie(a, b, c) = t;
  cout << a << " " << b << " " << c << endl;
  
  return 0;
}

2015年4月21日火曜日

C++のconstの話

 少しトリッキーなconstの話。constキーワードを何処に付けるかによって
  1. constなデータを指すポインタ
  2. データを指すconstなポインタ
  3. constなデータを指すconstなポインタ
という違いが生じる。

百聞は一見に如かずということでコードを。
int main(int argc, char **argv) {

    const int x = 5;
    int y = 10;

    const int *p = &x;    // a pointer that points to a const int variable
    ++p;    // OK
    ++*p;   // NG

    int * const q = &y;  // a const pointer that points to an int variable
    ++q;    // NG
    ++*q;   // OK
    
    const int * const r = &x;   // a const pointer that points to a const int variable
    ++r;    // NG
    ++*r;   // NG

    return 0;
}

2015年4月19日日曜日

C++マルチスレッド入門

まえがき
先日のCode Jamで並列処理を行えばゴリ押しで解ける問題が出題された。
本番中ゴリ押し解を思いつくには思いついたのだが、C++でマルチスレッドの処理を書いたことが無くて、ごにょごにょやってるうちにタイムアップとなってしまった。

 せっかくなので、C++でマルチスレッドのプログラムの書き方を勉強してみた。後で見返した時にすぐ思い出すように簡単にまとめておく。
  1. マルチスレッド版Hello, World
  2. 子スレッドに引数を渡す
  3. mutexを使ったロック
  4. 子スレッドから結果を受け取る
  5. 子スレッドに後から引数を渡す

1. マルチスレッド版Hello, World
threadクラスのインスタンスを作成すると、スレッドが作成される。
スレッド化したい処理はコンストラクタで渡す。
コンストラクタに渡す処理は、関数でもファンクタでもラムダ式でもよい。
threadインスタンスのjoin()を呼ぶとスレッドの完了を待つ。
detachを呼ぶとスレッドの完了を待たずにmainは終了する。
#include <iostream>
#include <thread>

using namespace std;

int main(int argc, char **argv) {
    
    thread t1([]() { cout << "Hello, World!" << endl; });
    
    // do other tasks

    t1.join();       // wait t1 to finish
    //t1.detach();   // do not wait t1 to finish

    return 0;
}

2. 子スレッドに引数を渡す
スレッド化したい処理に引数がある場合は、threadのコンストラクタの第2引数以降で指定する。
引数を参照で渡したい場合は、refを使って参照ラッパーを渡す。
#include <iostream>
#include <thread>
#include <vector>

using namespace std;

void sayHello(vector<string> &names) {
    for (auto &x : names)
        cout << "Hello, "<< x << "." << endl;
}

int main(int argc, char **argv) {
    
    vector<string> names{ 
        "taro", 
        "hanako", 
        "jiro", 
        "mika"
    };

    thread t1(sayHello, ref(names));

    t1.join();

    return 0;
}
3. mutexを使ったロック
複数スレッドで共有するリソースを使う場合はmutexで排他制御を行う。
mutexクラスのlock、unlockメソッドを直接使うのは非推奨。RAIIでmutexの管理を行う標準ラッパークラスが提供されているのでそれを使う。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>

using namespace std;

mutex mtx_stdout;

void work(int t) {

    {
        lock_guard<mutex> guard(mtx_stdout);  // RAII
        cout << "work: " << t << " begin." << endl;
    }

    //....
    this_thread::sleep_for (chrono::seconds(3));
    
    {
        lock_guard<mutex> guard(mtx_stdout);  // RAII
        cout << "work: " << t << " end." << endl;
    }

}

int main(int argc, char **argv) {

    vector<thread> threads(10);

    for (int i = 0; i < 10; i++)
        threads[i] = thread(work, i+1);
    
    for (auto &x : threads)
        x.join();
    
    return 0;
}
より柔軟な制御を行いたい場合は、unique_lockを使うという選択肢もある。
void work(int t) {
    unique_lock<mutex> locker(mtx_stdout, defer_lock);

    locker.lock();
    cout << "work: " << t << " begin." << endl;
    locker.unlock();

    //....
    this_thread::sleep_for (chrono::seconds(3));
    
    locker.lock();
    cout << "work: " << t << " end." << endl;
    locker.unlock();
}

5. 子スレッドから結果を受け取る
子スレッドから処理結果を受け取りたい場合は、futureを使う。
asyncの第一引数にlaunch::asyncを指定することで別スレッドで処理を開始出来る。
第一引数にlaunch::deferredを指定した場合は、処理が結果取得時まで遅延される。(※別スレッドは作成されないことに注意)
#include <future>
#include <vector>
#include <iostream>

using namespace std;

bool isPrime(long long n) {
    if (n < 2)
        return false;
    
    for (long long i = 2; i * i <= n; i++)
        if (n % i == 0)
            return false;
    
    return true;           
}

int main(int argc, char **argv) {

    future<bool> fut = async(launch::async, isPrime, 1000000007);
    cout << fut.get() << endl;

    return 0;
}
6. 子スレッドに後から引数を渡す
スレッド実行時に引数の値が分かっておらず、後から非同期で子スレッドに引数を渡したい場合はpromiseを使う。
#include <future>
#include <vector>
#include <iostream>

using namespace std;

bool test(int x, future<int> &f) {
    return x < f.get();
}

int main(int argc, char **argv) {

    promise<int> p;
    future<int> f = p.get_future();

    future<bool> fut = async(launch::async, test, 10, ref(f));

    p.set_value(12);
    cout << fut.get() << endl;

    return 0;
}

スレッドに参照を渡したい場合はrefを使う

 C++でスレッドに参照を渡すときの話。

まずダメな例から。
#include <iostream>
#include <vector>
#include <future>

using namespace std;

int solve(vector<int> &x) {
    x[0] += 100;
    return x[0];
}

int main(int argc, char **argv) {
    
    vector<int> v{1,2,3};

    future<int> fut = async(launch::async, solve, v);

    cout << fut.get() << endl;
    cout << v[0] << endl;

    return 0;
}
上記のプログラムを実行すると、

101
1

と表示される。vector<int> v は参照渡しされているように見えるが、実際はされていない。

スレッドに参照渡しをする場合は、以下のようにrefを使って参照ラッパーを渡すようにしなければならない。
#include <iostream>
#include <vector>
#include <future>

using namespace std;

int solve(vector<int> &x) {
    x[0] += 100;
    return x[0];
}

int main(int argc, char **argv) {
    
    vector<int> v{1,2,3};

    future<int> fut = async(launch::async, solve, ref(v));

    cout << fut.get() << endl;
    cout << v[0] << endl;

    return 0;
}

実行結果は、
101
101
となる。

2014年12月24日水曜日

知ってると便利なSTL(12) reverse_iterator

基本
逆方向に進むiterator。
rbeginはend - 1と同じ場所を指す。rendはbegin - 1と同じ場所を指す。

上の説明を理解するための簡単なサンプル。
#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char **argv) {
    
    vector<int> v{1,2,3,4,5,6,7,8};
    for (auto itr = v.rbegin(); itr != v.rend(); itr++) {
        cout << *itr << endl;
    }

    return 0;
}
実行すると、
8
7
6
5
4
3
2
1
と表示される。v.rbegin()はv.end()-1の場所を指しており、v.rend()はv.begin()-1の場所を指していることが分かる。また、reverse_iteratorは名前の通り逆向きに進むことが分かる。

これだけだと何が嬉しいか分からないので、以下に便利な使い方を示す。

逆順ソート
vectorを逆順にソートしたいときに、reverse_iteratorを使うと短く書ける。
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(int argc, char **argv) {
    
    vector<int> v{1,3,5,7,2,4,6,8};

    // functorを使って書くこともできるが、
    sort(v.begin(), v.end(), greater<int>());
    
    // reverse_iteratorを使うと、短く書ける
    sort(v.rbegin(), v.rend());

    return 0;
}

setの最小値、最大値
beginで最小値、rbeginで最大値を取得出来る。
#include <iostream>
#include <set>

using namespace std;

int main(int argc, char **argv) {
    
    set<int> s{3,2,1,4,8,7,6,5};

    cout << "min = " << *s.begin() << endl;
    cout << "max = " << *s.rbegin() << endl;

    return 0;
}

2014年9月23日火曜日

単語が辞書に存在するかどうかを調べる


 暗号化された英語の文章を復号するという問題に挑戦しています。
復号化された文章が正しい英単語から構成されていることを確認するために、単語が辞書に存在するかどうかをチェックする機能を実装しました。Ubuntuの場合は、/usr/share/dict/american-englishにアメリカ英語の単語リストが存在するので、それを使いました。  
#include <iostream>
#include <fstream>
#include <set>

using namespace std;

class dictionary {
    set<string> container;

public:
    void load(const string &path) {
        ifstream fs(path);

        string word;
        while (fs >> word)
            container.insert(word);
    }

    bool contains(const string &word) {
        return container.count(word);
    }
};


int main(int argc, char **argv) {

    dictionary dict;
    dict.load("/usr/share/dict/american-english");

    for (string s; cin >> s; ) {
        if (dict.contains(s))
            cout << s << " is in the dictionary." << endl;
        else
            cout << s << " is not in the dictionary." << endl;
    }

    return 0;
}
以下実行結果です。固有名詞も辞書に含まれているみたいです。
hello
hello is in the dictionary.
world
world is in the dictionary.
soccer
soccer is in the dictionary.
Linux
Linux is in the dictionary.
Beatles
Beatles is in the dictionary.
Fibonacci
Fibonacci is in the dictionary.
totient
totient is not in the dictionary.
Yankees
Yankees is in the dictionary.

2014年8月31日日曜日

C++11でint、string間の変換が簡単になった

C++11でint、string間の変換が簡単に出来るようになっていた。
これは楽だ。
#include <iostream>

using namespace std;

int main(int argc, char **argv) {
    
    int n = 123456789;
    string s = to_string(n);
    int m = stoi(s);

    cout << s << endl;    // 123456789
    cout << m << endl;    // 123456789

    return 0;
}

2014年7月11日金曜日

連分数

 連分数関連の処理をC++で書いて遊んでみました。

 連分数の基本については、Continued Fraction By Kardi Teknomo, PhD が分かりやすかったです。
 冪級数展開が発散するときでも連分数展開は収束する場合があり、かつ、どちらも収束するようなケースでは連分数展開の方が収束が早い場合が多いらしく、実用的なトピックです。黄金比やネイピア数を連分数展開すると綺麗な形になるというのもまた興味を唆られます。

分数を連分数展開する
p/qを連分数展開する場合を考えます。

p/q = [p/q] + (p%q)/q = [p/q] + 1/(q/(p%q))

となります。
ただし、[]はfloor関数です。

a0 = [p/q], a1 = [q/(p%q)], ....となるのでこれを再帰的に考えていくとユークリッドの互除法と同じパターンになることが分かります。
#include <iostream>
#include <vector>

using namespace std;

/*
 * p/q を連分数展開する
 */
void expand(int p, int q, vector<int> &terms) {
    if (q == 0)
        return;

    terms.push_back(p / q);
    expand(q, p % q, terms);

}

template <typename T>
void dump(const vector<T> &vs) {
    for (size_t i = 0; i < vs.size(); i++)
        cout << vs[i] << " ";
    cout << endl;
}

int main() {
    vector<int> terms;
    expand(109, 48, terms);
    dump(terms);  // 2 3 1 2 4 

    return 0;
}

連分数を分数に変換する

ボトムアップに計算する方法(右結合則を使って項数を減らしていくようなイメージ)もありますが、トップダウンに計算する方法の方が面白そうだったのでそちらをやってみます。

p0 = a0,  q0 = 1,
p1 = a1 a0 + 1,  q1 = a1, 
pk = akpk-1 + pk-2(k > 1),
qk = akqk-1 + qk-2(k > 1).

とすると、

[a0; a1, a2, ..., ak] = pk / qk

が成り立ちます(帰納法で証明できます)。

これを使えば連分数を分数に変換出来ます。
因みにakがすべて1の場合は、pkとqkが隣り合うフィボナッチ数になるため、連分数[1; 1,1,1,1,1,1,...]は黄金比になります。
#include <iostream>
#include <vector>
#include <iomanip>

using namespace std;

/*
 * 連分数を分数に変換する
 */
pair<int, int> compute(const vector<int> &terms) {
    int p[] = {terms[0], 1};
    int q[] = {1, 0};

    int len = terms.size();
    for (int i = 1; i < len; i++) {
        p[i & 1] = terms[i] * p[(i-1) & 1] + p[i & 1]; 
        q[i & 1] = terms[i] * q[(i-1) & 1] + q[i & 1];
    }
    --len;

    return make_pair(p[len & 1], q[len & 1]);
}


int main() {
    
    vector<int> terms(30, 1);
    pair<int, int> frac = compute(terms);
    cout << fixed << setprecision(15);
    cout << 1. * frac.first / frac.second << endl;   // 1.618033988750541

    frac = compute(vector<int> {2, 3, 1, 2, 3, 1});
    cout << fixed << setprecision(15);
    cout << 1. * frac.first / frac.second << endl;   // 2.270833333333333

    return 0;
}
実数を分数に変換する
実は、これをやりたく連分数について勉強したのです。

実数xを分数p/qで近似せよ。

という問題を考えます。
  1. xを連分数展開する。
  2. 連分数を分数に変換する。
手順でp/qを導出することが出来ます。かなりテキトーなコードですが、お遊びレベルではそれっぽく動いています。
#include <cstdio>
#include <cmath>

using namespace std;

/*
 * 実数を分数に変換する
 */
void compute(double x, double eps = 1e-9) {

    double y = x;
    int a = (int)y;

    if (abs(a - x) < eps) {
        printf("%d / %d = %.15lf (diff = %.15lf)\n", a, 1, 1.*a, abs(a - x));
        return;
    }

    y = 1 / (y - a);
    int p[] = {a, 1};
    int q[] = {1, 0};

    int i = 0;
    while (i < 30) {
        a = (int)y;
        y = 1 / (y - a);
        ++i;
        p[i & 1] = a * p[(i-1) & 1] + p[i & 1]; 
        q[i & 1] = a * q[(i-1) & 1] + q[i & 1];

        if (abs(1. * p[i & 1] / q[i & 1] - x) < eps)
            break;
    }

    printf("%d / %d = %.15lf (diff = %.15lf)\n", 
           p[i&1], q[i&1], 1. * p[i&1] / q[i&1], abs(1. * p[i & 1] / q[i & 1] - x));
}

int main() {
    
    compute(sqrt(2));  // 47321 / 33461 = 1.414213562057320 (diff = 0.000000000315775)     
    compute(sqrt(3));  // 51409 / 29681 = 1.732050806913514 (diff = 0.000000000655363)  
    compute(M_PI);     // 103993 / 33102 = 3.141592653011902 (diff = 0.000000000577891) 
    compute(1.222);    // 611 / 500 = 1.222000000000000 (diff = 0.000000000000000)      

    return 0;
}
実は昔同じ問題を考察していたのを思い出しました。このときは二分探索/しゃくとり法で解こうとしていたみたいです。
今回連分数を勉強して、より実用的なツールを手に入れたなという感じです。