ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

qiskitrcファイルの場所と、IBMQ.load_account()について

qiskitrcファイルの場所

C:\Users\[ユーザ名]\.qiskit の中にあります。

qiskitrcファイルって何?(IBMQ.load_account()について)

IBMQから貰った自分のトークンを使えば、IBMQにジョブを投げて実機の量子コンピュータで量子回路を実行することができます。

そのとき、以下のようなコードを書くと思います。

from qiskit import IBMQ

# 自分のトークンをローカルPCにセーブする
IBMQ.save_account('MY_API_TOKEN')

# アカウント情報をロードする
IBMQ.load_account()

実はIBMQ.save_account('MY_API_TOKEN')を実行した時点で C:\Users\[ユーザ名]\.qiskitqiskitrc ファイルが作成され、そこにIBMQのトークン情報が保存されます。

そして IBMQ.load_account() ではこのqiskitrc ファイルを読み込んでいます。

なので、毎回 IBMQ.save_account('MY_API_TOKEN') は実行する必要ありません。トークン情報は他人に漏れたらだめな重要な情報なので、うっかりGitHubなどに上げないためにもこの行はさっさと消すことをおすすめします。

IBMQAccountCredentialsNotFound エラー

IBMQ.load_account() したときに以下のエラーが出る場合があります。

IBMQAccountCredentialsNotFound            Traceback (most recent call last)
<ipython-input-6-05e731fbbf7d> in <module>
      1 from qiskit import IBMQ
      2 
----> 3 IBMQ.load_account()

c:\Users\username\workspace\my-practice\qiskit_textbook_practice\venv\lib\site-packages\qiskit\providers\ibmq\ibmqfactory.py in load_account(self)
    166 
    167         if not credentials_list:
--> 168             raise IBMQAccountCredentialsNotFound(
    169                 'No IBM Quantum Experience credentials found.')
    170 

IBMQAccountCredentialsNotFound: 'No IBM Quantum Experience credentials found.'

このエラーはIBMQ.load_account() 時に、 qiskitrc ファイルがなくて認証情報を読み込めないときに発生します。

save_account('MY_API_TOKEN') をして qiskitrc ファイルを作成しましょう。

Qiskitの”DAGCircuitError: 'expected %d wires, got %d'”エラー

Qiskitで、以下のようなエラーが出ることがあります。

---------------------------------------------------------------------------
DAGCircuitError                           Traceback (most recent call last)
<ipython-input-46-607b691d6215> in <module>
      3 backend = Aer.get_backend("qasm_simulator")
      4 shots = 1024
----> 5 results = execute(dj_qc, backend=backend, shots=shots).result()
      6 count = results.get_counts()
      7 plot_histogram(count)

(省略)

DAGCircuitError: 'expected 4 wires, got 7'

「DAGCircuitError」で適当にググったら以下のサイトが見つかります。

https://qiskit.org/documentation/_modules/qiskit/dagcircuit/dagcircuit.html#:~:text=raise%20DAGCircuitError(%22expected%20%25d%20wires%2C%20got%20%25d%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%25%20(wire_tot%2C%20len(wires)))

エラー名でなんとなく想像がつくのですが、このエラーは「4つのワイヤ数が期待されているのに、実際は7本のワイヤ数になってます。ワイヤ数が合ってないですよ」というエラーです。

たとえば以下のような回路でエラーが発生したとします。

f:id:takeg:20210626013855p:plain
量子回路

ここで「期待されている4本のワイヤ」というのはqubitsのワイヤ数のことです。上の回路ではq0~q3までの4本のワイヤがあるので数は合ってそうですが、"blackbox"と書かれた回路が怪しそうです。

この"blackbox"回路は以下のように作成しました。

oracle = QuantumCircuit(q_in+1, cbits, name="blackbox")
for i in range(q_in):
    oracle.cx(i, q_in)

dj_qc.append(oracle, range(q_in+1))

cbitsClassicalRegister数を指定しているのですが、これが元の回路dj_qcappendするときに指定し忘れているので、「ワイヤ数が合っていない」というエラーが発生しています。

このブラックボックス回路の場合、直し方は2通りあります。

【方法1】ブラックボックス回路はcbitsを使ってないので、作らない

この"blackbox"回路は無駄に古典ビット用の回路を作っていますが、別に使う気はないので消せばいいです。

この場合、以下のようなコードになります。

oracle = QuantumCircuit(q_in+1, name="blackbox")
for i in range(q_in):
    oracle.cx(i, q_in)

dj_qc.append(oracle, range(q_in+1))

【方法2】cbitsも元の回路にappendする

qc.appendでは、引数はqargscargsがあり、それぞれ量子ビットと古典ビットを指定できます。なので、きちんとそれぞれ指定してやります。

この場合、以下のようなコードになります。

oracle = QuantumCircuit(q_in+1, cbits, name="blackbox")
for i in range(q_in):
    oracle.cx(i, q_in)

dj_qc.append(oracle, qargs=range(q_in+1), cargs=range(cbits))

VSCodeのipynbファイルの「Select a Kernel」に、venvの環境が表示されないときの対処法

  • 対処法1:右クリックの「Codeを開く」を使わず、別のランチャーからVSCodeを開くと表示される
  • 対処法2:ipython kernel installカーネルを追加する
  • その他:Jupyterの拡張機能をダウングレードする(v2020.12.414227025では動くらしいが、あまりしたくない)

似たような現象の議論は以下で行われています。

https://github.com/microsoft/vscode-jupyter/issues/4011

どのような現象か

VSCode上でipynbファイルを開いたとき、以下のような画面になります。

f:id:takeg:20210522153208p:plain
VSCodeでipynbファイルを開いたときの状況

venvで仮想環境を作ったので、ipynbで実行されるPython./venv/Scripts/python.exeに変更したいです。

以下のように、VSCode画面右上の「Python 3.8.5 64-bit ('base': conda): idle」をクリックすると実行するPythonの環境を変更できますが、venv環境のpython.exeが表示されず、設定できないという現象が発生します(手動で設定したいが、方法がわからない)。

f:id:takeg:20210522153648p:plain

この現象は、起きたり起きなかったりと再現性はなく、どういうルールで「Select a Kernel」に表示されるのかもわからない(自分で設定させてほしい)うえ、PCを再起動しても直るわけでもないので、 結構途方に暮れる現象です。

対処法1:右クリックの「Codeを開く」を使わず、別のランチャーからVSCodeを開くと表示される

VSCodeをタスクバーにピン留めしておいて、そこからVSCodeを起動すると、なぜか「Select a Kernel」で選択可能になっています。

f:id:takeg:20210522163604p:plain
右クリックの「Codeを開く」をした場合の「Select a Kernel」

f:id:takeg:20210522163728p:plain
タスクバーにピン留めしたVSCodeで起動した場合の「Select a Kernel」

タスクバーにピン留めした方は、一番上に「c:\Users\takey\Desktop\myspace\workspace\my-practice\qiskit_textbook_practice\venv\Scripts...」があるのがわかります。これを選択したかった。

ただ、根本的な解決になっていない気がする。

対処法2:ipython kernel installカーネルを追加する

Jupyterで複数カーネルを簡単に選択するための設定 - Qiita

# カーネルに追加
(venv)$  ipython kernel install --user --name=venv --display-name=venv

これを実行してVSCodeを再起動すると、「Select a Kernel」に「venv」という名前のカーネルが選択可能になります。ただし、--nameで指定した名前が被ると上書きしてしまうので、それに注意して追加しなければなりません。

私は仮想環境名をすべてvenvに統一しているのですが、この方法はいちいち名前を考えなきゃいけないというデメリットがある一方、Jupyterの暗黙的なルールで「Select a Kernel」に表示されるかされないかに対して、明示的に「追加」という操作を行えるのがメリットです。

# カーネルを表示
(venv)$ jupyter kernelspec list
Available kernels:
  venv                                                        C:\Users\takey\AppData\Roaming\jupyter\kernels\venv

# 削除
(venv)$  jupyter kernelspec uninstall venv

トラブルシューティング

対処法2でカーネルを追加したけど、Pythonのパスがおかしい。

たとえばipython kernel install --user --name=hoge_venv --display-name=hoge_venvをして、hoge_venvカーネルを作成したとします。

そのhoge_venv環境のPythonがローカルの仮想環境であるvenv環境のパスではなく、たとえばcondaのPythonのパスになっていることがあります。

この場合、ipython kernel installしたときのipythonがconda環境のものを使っていることが原因と考えられます。

なので、仮想環境にpip install ipythonipythonをインストールしてやればよいです。

対処法2でカーネルを追加したけど、「Python requires ipykernel to be installed.」ポップアップが出る

github.com

カーネルhoge_venvに切り替えて、ipynbファイルのセルを実行したとき、上記のサイトのように「Python 3.8.3 64-bit ('base': conda) requires ipykernel to be installed.」というポップアップがでることがあります。

「install」ボタンを押しても「Could not install ipykernel. If pip is not available, ...」というメッセージが表示され、インストールに失敗します。

これの意味不明なところは、私の場合、このときメインではPython3.7系を使っていたはずなのですが、「Python3.8.3 64-bit (...) requires ...」と表示されていました。どうやら一番上に表示されているカーネルを参照してしまっているようです。

これの直し方ですが、VSCodePython拡張機能を一旦アンインストールし、再インストールすれば直りました。

【追記】VSCodeを再起動したら再度このポップアップが出るようになりました。VSCodeを右クリックで開くのではなく、タスクバーにピン留めして、そこから起動するとポップアップが出なくなったので(意味不明)、とりあえずそれで運用しています。

(ちなみにVSCodeの設定は、VSCodeの再起動をしないと設定が反映されないことが多いので、なんか反映していないと思ったらこまめにVSCodeを再起動するとよいです。)

SoundHound Inc. Programming Contest 2018 -Masters Tournament- のC問題解いた

問題:SoundHound Inc. Programming Contest 2018 -Masters Tournament- C - Ordinary Beauty

  • 1~nの目のサイコロがある
  • m回サイコロを振って、出た目を数列 (a_1, a_2, ... , a_{m})とする
  • 数列の隣り合う2項の差がdの個数を、その数列の美しさとする
  • 数列の美しさの期待値 Eを求めよ

みたいな問題。

解説

  • 隣り合う2項は m-1個ある
    •  (a_1, a_2), (a_2, a_3), ...,(a_{m-1}, a_m) m-1
  • 解説PDFにあるように、「期待値の線形性より、m-1通りそれぞれにおいて、差の絶対値がdである確率を求めて、全部足せばよい」
  • ある隣り合う2項の美しさを x_i、その発生確率を P_iとすると、ある隣り合う2項の美しさの期待値は \Sigma_i{x_i P_i}である。
    •  i=0,1
    •  x_0 = 0
    •  x_1=1
  • 求めたい数列の期待値 Eは、 E=(m-1)\Sigma_i{x_iP_i}
    •  x_0=0より、 E = (m-1)x_1 P_1となる。
    • さらに x_1=1より、 E = (m-1)P_1となる。
  • なので発生確率 P_1を求めればよい
  • 隣り合う2項を (a, b)と表したとき、この a bの差の絶対値が dである(すなわち x_1=1である)発生確率 P_1を求めたい。
    •  d = 0のとき
      • 美しさの条件を満たすのは
        •  (1,1),(2,2),...,(n,n) n通り
      • よって、発生確率 P_1 \frac{n}{n^{2}} = \frac{1}{n}(分母の n^{2}は、aが n通り、bが n通りだから)
    •  d\neq0のとき
      • 美しさの条件を満たすのは
        •  (1,1+d), (2, 2+d), ..., (n-d, n)
        •  (1+d, 1), (2+d, 2), ..., (n, n-d)
        •  2(n-d)通り
      • よって発生確率 P_1は、 \frac{2(n-d)}{n^{2}}
  • これで求める期待値 E=(m-1)P_1が求まった。

実装

C++での解法です。

#include <bits/stdc++.h>
#define _USE_MATH_DEFINES  // M_PI等のフラグ
#define MOD 1000000007
#define COUNTOF(array) (sizeof(array)/sizeof(array[0]))
#define rep(i,n) for (int i = 0; i < (n); ++i)
#define intceil(a,b) (a+(b-1))/b
using namespace std;
using ll = long long;
using pii = pair<int,int>;
using pll = pair<long,long>;
const long long INF = LONG_LONG_MAX - 1001001001;
void chmax(int& x, int y) { x = max(x,y); }
void chmin(int& x, int y) { x = min(x,y); }


void solve() {
    // 隣り合う2項はm-1通りある
    // 2項(a,b)のa,bの絶対値の差がdである確率を求めて、(m-1)倍すればよい
    ll n, m, d; cin >> n >> m >> d;
    double ans;
    if (d==0) {
        // (1,1),...(n,n)のn通り
        ans = (double)n/pow(n,2);
        ans *= (m-1);
    }
    else {
        // (1, 1+d), (2, 2+d), ..., (n-d, n)
        // (1+d, 1), (2+d, 2), ..., (n, n-d) の2(n-d)通り
        ans = (double)2*(n-d)/pow(n,2);
        ans *= (m-1);
    }
    printf("%.10lf", ans);
}


int main() {
    solve();
    return 0;
}

C++で行列計算ライブラリを自作してみた

C++の独習がてら行列計算ライブラリを自作してみました。

なんでこんなことしたかと言うと、普段競プロしてたら他人の作ってくれた便利なmintライブラリを使ったりするのですが、演算子オーバーロードの文法とかをよくわかってないままコピペして使ってたので、理解するがてら自分でも何か自作しようと思ったからです。

実務で使うならコードをヘッダファイルに分けるべきなんですが、競プロで使えたらなーという期待で.cppファイルにベタ書きしてます。

調べたらC++には行列計算ライブラリにEigenというのがあるので、実際に実用するならそっちを使ったほうがいいでしょう。あと後述してますが行列式の計算は重いです。改善の余地ありですね。

コード

#include <bits/stdc++.h>
using namespace std;


namespace MatrixLib {
    class print_format_error: public std::exception {
        virtual const char* what() const noexcept { return "Error: print() method cannot print these values because of unsupported!"; }
    };
    class matrix_product_error: public std::exception {
        virtual const char* what() const noexcept { return "Error: Impossible to calculate matrix product!"; }
    };
    class determinant_error: public std::exception {
        virtual const char* what() const noexcept { return "Error: Impossible to calculate determinant!"; }
    };
    class inversion_error: public std::exception {
        virtual const char* what() const noexcept { return "Error: Impossible to calculate inversion!"; }
    };


    /*** 二次元行列のライブラリMatrix ***/
    template <typename T>
    class Matrix {
        private:
            vector<vector<T>> A;

            T __cofactor(const vector<vector<T>>& coA) const {
                /* 各余因子の計算を再帰的にする */
                if (coA.size() == 1) return coA[0][0];
                if (coA.size() == 2) {
                    return coA[0][0]*coA[1][1]-coA[0][1]*coA[1][0];
                }

                T res = 0;
                for (int col2=0; col2<coA.size(); col2++) {
                    vector<vector<T>> cocoA(coA.size()-1);
                    for (int row=1; row<coA.size(); row++) {
                        for (int col=0; col<coA.size(); col++) {
                            if (col2==col) continue;
                            cocoA[row-1].emplace_back(coA[row][col]);
                        }
                    }
                    if (!(col2&1)) res += coA[0][col2] * __cofactor(cocoA);
                    else res -= coA[0][col2] * __cofactor(cocoA);
                }
                return res;
            }
        public:
            size_t row;
            size_t col;
            Matrix(size_t row=1, size_t col=1) {
                this->row = row;
                this->col = col;
                this->A.resize(row);
                for(size_t i=0; i<row; i++) this->A[i].resize(col);
            }

            vector<T>& operator[](const size_t i) {
                return this->A[i];
            }

            Matrix& operator+=(Matrix other) {
                for(size_t i=0; i<this->row; i++) {
                    for(size_t j=0; j<this->col; j++) {
                        this->A[i][j] += other[i][j];
                    }
                }
                return *this;
            }
            Matrix operator+(const Matrix other) const {
                return Matrix(*this) += other;
            }
            Matrix& operator+=(const double a) {
                /* 各要素にaを足す */
                for (size_t i=0; i<this->row; i++) {
                    for (size_t j=0; j<this->col; j++) {
                        this->A[i][j] += a;
                    }
                }
                return *this;
            }
            Matrix operator+(const double a) const {
                return Matrix(*this) += a;
            }

            Matrix& operator-=(Matrix other) {
                for(size_t i=0; i<this->row; i++) {
                    for(size_t j=0; j<this->col; j++) {
                        this->A[i][j] -= other[i][j];
                    }
                }
                return *this;
            }
            Matrix operator-(const Matrix other) const {
                return Matrix(*this) -= other;
            }
            Matrix operator-() const {
                Matrix res(this->row, this->col);
                for (size_t i=0; i<this->row; i++) {
                    for (size_t j=0; j<this->col; j++) {
                        res[i][j] = -this->A[i][j];
                    }
                }
                return res;
            }
            Matrix& operator-=(const double a) {
                /* 各要素にaを引く */
                for (size_t i=0; i<this->row; i++) {
                    for (size_t j=0; j<this->col; j++) {
                        this->A[i][j] -= a;
                    }
                }
                return *this;
            }
            Matrix operator-(const double a) const {
                return Matrix(*this) -= a;
            }

            Matrix& operator*=(Matrix other) {
                /* NxN行列 x NxN行列 の積を求める */
                if (!(this->row==this->col && other.row==other.col && this->row==other.row)) throw matrix_product_error();

                vector<vector<T>> res(this->row, vector<T>(this->col, 0));
                for(size_t i=0; i<other.row; i++) {
                    for (size_t j=0; j<this->col; j++) {
                        for (size_t k=0; k<this->col; k++) {
                            res[i][j] += this->A[i][k]*other[k][j];
                        }
                    }
                }
                this->A = res;
                return *this;
            }
            Matrix operator*(Matrix other) const {
                /* ixk行列 * kxj行列 の積を求める */
                if (this->col!=other.row) throw matrix_product_error();

                Matrix<T> res(this->row, other.col);
                for (size_t i=0; i<this->row; i++) {
                    for (size_t j=0; j<other.col; j++) {
                        for (size_t k=0; k<this->col; k++) {
                            res[i][j] += this->A[i][k]*other[k][j];
                        }
                    }
                }
                return res;
            }

            Matrix& operator*=(const double a) {
                /* 各要素にaをかける */
                for (size_t i=0; i<this->row; i++) {
                    for (size_t j=0; j<this->col; j++) {
                        this->A[i][j] *= a;
                    }
                }
                return *this;
            }
            Matrix operator*(const double a) const {
                return Matrix(*this) *= a;
            }

            void print() {
                /* 行列の状態を表示する */
                string format;
                if (typeid(T)==typeid(int)) format = "%*d"s;
                else if (typeid(T)==typeid(unsigned int) || typeid(T)==typeid(unsigned short)) format = "%*u"s;
                else if (typeid(T)==typeid(long)) format = "%*ld"s;
                else if (typeid(T)==typeid(unsigned long)) format = "%*lu"s;
                else if (typeid(T)==typeid(long long)) format = "%*lld"s;
                else if (typeid(T)==typeid(unsigned long long)) format = "%*llu"s;
                else if (typeid(T)==typeid(short)) format = "%*f"s;
                else if (typeid(T)==typeid(double)) format = "%*lf"s;
                else if (typeid(T)==typeid(long double)) format = "%*LF"s;
                else throw print_format_error();

                int len = 0;
                for (size_t i=0; i<this->row; i++) {
                    for (size_t j=0; j<this->col; j++) {
                        string str = to_string(this->A[i][j]);
                        len = max(len, (int)str.size());
                    }
                }

                printf("[[");
                for (size_t i=0; i<this->row; i++) {
                    for (size_t j=0; j<this->col; j++) {
                        printf(format.c_str(), len, this->A[i][j]);
                        if (j!=this->col-1) printf(", ");
                        else printf("]");
                    }
                    if (i!=this->row-1) printf(",\n  ");
                    else printf("]\n");
                }
            }

            T det() const {
                /* 行列式を計算して返す */
                if (this->row!=this->col) throw determinant_error();

                return this->__cofactor(this->A);
            }

            Matrix dot(const Matrix B) const {
                /* ドット積(内積)計算をする */
                return Matrix(*this) * B;
            }

            Matrix inv() const {
                /* 逆行列を返す(掃き出し法) */
                if (!(this->row==this->col)) throw inversion_error();

                size_t N = this->row;
                Matrix<T> A = *this;
                Matrix<T> invA(N, N);
                for (size_t i=0; i<N; i++) {
                    for (size_t j=0; j<N; j++) {
                        invA[i][j] = (i==j) ? 1 : 0;
                    }
                }

                for (size_t i=0; i<N; i++) {
                    T buf = 1/A[i][i];
                    for (size_t j=0; j<N; j++) {
                        A[i][j] *= buf;
                        invA[i][j] *= buf;
                    }

                    for (size_t j=0; j<N; j++) {
                        if (i!=j) {
                            buf = A[j][i];
                            for (size_t k=0; k<N; k++) {
                                A[j][k] -= A[i][k]*buf;
                                invA[j][k] -= invA[i][k]*buf;
                            }
                        }
                    }
                }

                return invA;
            }
    };
}

GitHubにあげたコードは以下。

github.com

使い方

要素がdouble型の行列

void test(){
    cout << "===test===" << endl;
    MatrixLib::Matrix<double> mat1(3,3);  // 要素がdoble型の3x3行列mat1の作成
    MatrixLib::Matrix<double> mat2(3,3);  // 要素がdoble型の3x3行列mat2の作成
    MatrixLib::Matrix<double> res;
    for (int i=0; i<3; i++) {
        for (int j=0; j<3; j++) {
            mat1[i][j] = j+i*3;
            mat2[i][j] = 1;
        }
    }
    // 行列mat1の確認
    mat1.print();
// [[0.000000, 1.000000, 2.000000],
//   3.000000, 4.000000, 5.000000],
//   6.000000, 7.000000, 8.000000]]

    // 行列mat2の確認
    mat2.print();
// [[1.000000, 1.000000, 1.000000],
//   1.000000, 1.000000, 1.000000],
//   1.000000, 1.000000, 1.000000]]

    // 行数の取得
    cout << "mat1.row: " << mat1.row << endl;
    // mat1.row: 3

    // 列数の取得
    cout << "mat1.col: " << mat1.col << endl;
    // mat1.col: 3

    // 代入
    mat2[0][0] = 20;
    mat2.print();
// [[20.000000,  1.000000,  1.000000],
//    1.000000,  1.000000,  1.000000],
//    1.000000,  1.000000,  1.000000]]

    // 行列の+演算
    mat1 = mat1 + mat2;
    mat1.print();
// [[20.000000,  2.000000,  3.000000],
//    4.000000,  5.000000,  6.000000],
//    7.000000,  8.000000,  9.000000]]

    // 行列の+=演算
    mat1 += mat2;
    mat1.print();
// [[40.000000,  3.000000,  4.000000],
//    5.000000,  6.000000,  7.000000],
//    8.000000,  9.000000, 10.000000]]

    // 行列の-演算
    mat1 = mat1 - mat2;
    mat1.print();
// [[20.000000,  2.000000,  3.000000],
//    4.000000,  5.000000,  6.000000],
//    7.000000,  8.000000,  9.000000]]

    // 行列の-=演算
    mat1 -= mat2;
    mat1.print();
// [[0.000000, 1.000000, 2.000000],
//   3.000000, 4.000000, 5.000000],
//   6.000000, 7.000000, 8.000000]]

    // 各要素の符号を反転
    res = -mat1;
    res.print();
// [[-0.000000, -1.000000, -2.000000],
//   -3.000000, -4.000000, -5.000000],
//   -6.000000, -7.000000, -8.000000]]

    // 行列の*演算
    res = mat1 * mat2;
    res.print();
// [[  3.000000,   3.000000,   3.000000],
//    69.000000,  12.000000,  12.000000],
//   135.000000,  21.000000,  21.000000]]

    // 行列の*=演算
    mat1 *= mat2;
    mat1.print();
// [[  3.000000,   3.000000,   3.000000],
//    69.000000,  12.000000,  12.000000],
//   135.000000,  21.000000,  21.000000]]

    // mat1の各要素に1を足す(+演算子)
    res = mat1 + 1;
    res.print();
// [[  4.000000,   4.000000,   4.000000],
//    70.000000,  13.000000,  13.000000],
//   136.000000,  22.000000,  22.000000]]

    // mat1の各要素に1を足す(+=演算子)
    mat1 += 1;
    mat1.print();
// [[  4.000000,   4.000000,   4.000000],
//    70.000000,  13.000000,  13.000000],
//   136.000000,  22.000000,  22.000000]]

    // mat1の各要素に1を引く(-演算子)
    res = mat1 - 1;
    res.print();
// [[  3.000000,   3.000000,   3.000000],
//    69.000000,  12.000000,  12.000000],
//   135.000000,  21.000000,  21.000000]]

    // mat1の各要素に1を引く(-=演算子)
    mat1 -= 1;
    mat1.print();
// [[  3.000000,   3.000000,   3.000000],
//    69.000000,  12.000000,  12.000000],
//   135.000000,  21.000000,  21.000000]]

    // mat1の各要素を2倍する(*演算子)
    res = mat1 * 2;
    res.print();
// [[  6.000000,   6.000000,   6.000000],
//   138.000000,  24.000000,  24.000000],
//   270.000000,  42.000000,  42.000000]]

    // mat1の各要素を2倍する(*=演算子)
    mat1 *= 2;
    mat1.print();
// [[  6.000000,   6.000000,   6.000000],
//   138.000000,  24.000000,  24.000000],
//   270.000000,  42.000000,  42.000000]]

    // mat1の各要素を2.1倍する
    mat1 *= 2.1;
    mat1.print();
// [[ 12.600000,  12.600000,  12.600000],
//   289.800000,  50.400000,  50.400000],
//   567.000000,  88.200000,  88.200000]]
}

int main(int argc, char const *argv[]){
    test();
    return 0;
}

要素がint型の行列

void test2(){
    cout << "===test2===" << endl;
    MatrixLib::Matrix<int> mat1(2,3);  // 要素がint型の2x3行列mat1の作成
    MatrixLib::Matrix<int> mat2(3,2);  // 要素がint型の3x2行列mat2の作成
    mat1[0][0]=2; mat1[0][1]=1; mat1[0][2]=3;
    mat1[1][0]=5; mat1[1][1]=6; mat1[1][2]=4;

    mat2[0][0]=1; mat2[0][1]=2;
    mat2[1][0]=3; mat2[1][1]=2;
    mat2[2][0]=4; mat2[2][1]=3;

    // mat1の確認
    mat1.print();
// [[2, 1, 3],
//   5, 6, 4]]

    // mat2の確認
    mat2.print();
// [[1, 2],
//   3, 2],
//   4, 3]]

    // mat1とmat2の積
    MatrixLib::Matrix<int> mat = mat1 * mat2;
    mat.print();
// [[17, 15],
//   39, 34]]

    mat *= 2;
    mat.print();
// [[34, 30],
//   78, 68]]

    mat *= 2.1;  // 要素はint型なので、小数点以下は切り捨てられる
    mat.print();
// [[ 71,  63],
//   163, 142]]
}

行列式の計算

行列式の計算は余因子の計算を再帰的にしているのですが、計算量的に重いです。

調べたらもっと効率的で高速なやり方が以下のサイトに書いてありました。

Tech Tips: 行列式の計算

今回は余因子の再帰的な計算のままで放置してます。

行列式の計算確認は以下のサイトが便利でした。

行列式(n次元) - 高精度計算サイト

2x2の正方行列の行列式の計算。

void test_det2() {
    // 行列式確認用サイト
    // https://keisan.casio.jp/exec/system/1279265553
    cout << "===test_det2===" << endl;
    MatrixLib::Matrix<int> mat(2,2);  // int型の2x2行列
    mat[0][0]=3; mat[0][1]=1;
    mat[1][0]=5; mat[1][1]=1;
    mat.print();
// [[3, 1],
//   5, 1]]

    int d = mat.det();
    cout << "Det: " << d << endl;
    // -2
}

3x3の正方行列の行列式の計算。

void test_det3() {
    cout << "===test_det3===" << endl;
    MatrixLib::Matrix<int> mat(3,3);  // int型の3x3行列
    mat[0][0]=3; mat[0][1]=1; mat[0][2]=1;
    mat[1][0]=5; mat[1][1]=1; mat[1][2]=3;
    mat[2][0]=2; mat[2][1]=0; mat[2][2]=1;
    mat.print();
// [[3, 1, 1],
//   5, 1, 3],
//   2, 0, 1]]

    int d = mat.det();
    cout << "Det: " << d << endl;
    // 2
}

4x4の正方行列の行列式の計算。

void test_det4() {
    cout << "===test_det4===" << endl;
    MatrixLib::Matrix<int> mat(4,4);  // int型の4x4行列
    mat[0][0]=3; mat[0][1]=1; mat[0][2]=1; mat[0][3]=2;
    mat[1][0]=5; mat[1][1]=1; mat[1][2]=3; mat[1][3]=4;
    mat[2][0]=2; mat[2][1]=0; mat[2][2]=1; mat[2][3]=0;
    mat[3][0]=1; mat[3][1]=3; mat[3][2]=2; mat[3][3]=1;
    mat.print();
// [[3, 1, 1, 2],
//   5, 1, 3, 4],
//   2, 0, 1, 0],
//   1, 3, 2, 1]]

    int d = mat.det();
    cout << "Det: " << d << endl;
    // -22
}

5x5の正方行列の行列式の計算。

void test_det5() {
    cout << "===test_det5===" << endl;
    MatrixLib::Matrix<int> mat(5,5);  // int型の5x5行列
    mat[0][0]=3; mat[0][1]=1; mat[0][2]=1; mat[0][3]=2; mat[0][4]=1;
    mat[1][0]=5; mat[1][1]=1; mat[1][2]=3; mat[1][3]=4; mat[1][4]=1;
    mat[2][0]=2; mat[2][1]=0; mat[2][2]=1; mat[2][3]=0; mat[2][4]=1;
    mat[3][0]=1; mat[3][1]=3; mat[3][2]=2; mat[3][3]=1; mat[3][4]=1;
    mat[4][0]=1; mat[4][1]=1; mat[4][2]=1; mat[4][3]=1; mat[4][4]=1;
    mat.print();
// [[3, 1, 1, 2, 1],
//   5, 1, 3, 4, 1],
//   2, 0, 1, 0, 1],
//   1, 3, 2, 1, 1],
//   1, 1, 1, 1, 1]]

    int d = mat.det();
    cout << "Det: " << d << endl;
    // -14
}

ドット積の計算

行列のドット積の計算。やってることは*演算子と同じ。Pythonのnumpyライクに使えたら便利かなと思って作成。

void test_dot() {
    cout << "===test_dot===" << endl;
    MatrixLib::Matrix<int> mat1(3,3);
    MatrixLib::Matrix<int> mat2(3,3);
    MatrixLib::Matrix<int> res;
    mat1[0][0]=2; mat1[0][1]=1; mat1[0][2]=3;
    mat1[1][0]=5; mat1[1][1]=6; mat1[1][2]=4;
    mat1[2][0]=2; mat1[2][1]=1; mat1[2][2]=3;

    mat2[0][0]=1; mat2[0][1]=2; mat2[0][2]=3;
    mat2[1][0]=3; mat2[1][1]=2; mat2[1][2]=2;
    mat2[2][0]=4; mat2[2][1]=3; mat2[2][2]=1;

    mat1.print();
// [[2, 1, 3],
//   5, 6, 4],
//   2, 1, 3]]

    mat2.print();
// [[1, 2, 3],
//   3, 2, 2],
//   4, 3, 1]]

    // mat1とmat2のドット積(mat1*mat2と同じ)
    res = mat1.dot(mat2);
    res.print();
// [[17, 15, 11],
//   39, 34, 31],
//   17, 15, 11]]

    // 当然だが、mat1の値は変わらない
    mat1.print();
// [[2, 1, 3],
//   5, 6, 4],
//   2, 1, 3]]

    // mat2の値も変わらない
    mat2.print();
// [[1, 2, 3],
//   3, 2, 2],
//   4, 3, 1]]
}

逆行列の計算

逆行列の求め方は以下のサイトを参考にしました(掃き出し法)。

行列式の値の求め方、逆行列の作り方の C 言語プログラム | Blue Sky Lab

void test_inv() {
    // 計算確認用サイト
    // https://keisan.casio.jp/exec/system/1278758206
    cout << "===test_inv===" << endl;
    MatrixLib::Matrix<double> mat(4,4);
    mat[0][0]=3; mat[0][1]=1; mat[0][2]=1; mat[0][3]=2;
    mat[1][0]=5; mat[1][1]=1; mat[1][2]=3; mat[1][3]=4;
    mat[2][0]=2; mat[2][1]=0; mat[2][2]=1; mat[2][3]=0;
    mat[3][0]=1; mat[3][1]=3; mat[3][2]=2; mat[3][3]=1;
    mat.print();
// [[3.000000, 1.000000, 1.000000, 2.000000],
//   5.000000, 1.000000, 3.000000, 4.000000],
//   2.000000, 0.000000, 1.000000, 0.000000],
//   1.000000, 3.000000, 2.000000, 1.000000]]

    // 逆行列を計算して出力
    MatrixLib::Matrix<double> inv_mat = mat.inv();
    inv_mat.print();
// [[ 0.500000, -0.227273,  0.363636, -0.090909],
//    0.500000, -0.318182, -0.090909,  0.272727],
//   -1.000000,  0.454545,  0.272727,  0.181818],
//   -0.000000,  0.272727, -0.636364, -0.090909]]
}

ABC190 E問題

問題:AtCoder Beginner Contest 190: E - Magical Ornament

解説

解説動画まんまかもしれません。


魔法石が N個あります。

各魔法石は隣り合わせになっていいやつが決まっています。重要魔法石の集合 C_Kをすべて含んでいるように魔法石を1列に並べたとき、必要な魔法石の最小値はいくつですか?もし作れないなら-1を出力しなさい。という問題。


余談ですが、UnionFindすれば「作れない」のみを判定できる。意味ないけど。


入力例1の「隣り合っていい魔法石」をグラフで図示すると、以下のような感じになる。(赤が重要魔法石)

f:id:takeg:20210201162651p:plain
入力例1の「隣り合っていい魔法石」を表現したグラフ


この問題は、「重要魔法石を少なくとも1回通るときの、最小経路問題」と言い換えることができる。

直感的に、スタートは必ず重要魔法石からになりそうだと気づく。重要でない魔法石からスタートした場合、必ず余計な経路を通ったり、往復が必要になるからである。


難しいのが、「少なくとも1回通る」というのをどう考えればよいのかという部分である。

たとえば、以下のグラフを考える。

f:id:takeg:20210201163641p:plain
魔法石3は必ず2回通る必要がありそう

どう考えても、魔法石3は2回通る必要がありそうだ。これをどう実装すればいいのか皆目検討もつかなくてくまった。


ここで、重要でない魔法石をグラフから消して、重要魔法石のみを抜き出した重み付きグラフに変換してみると、解法が見えてくる。

f:id:takeg:20210201162832p:plain
重要魔法石のみ抜き出したグラフ

これでもまだ「少なくとも1回通る」というのをどう考えればいいのかわからないが、頂点数がKまで減った。Kは制約により17以下であり、めちゃくちゃ小さい。

なので、すべての重要魔法石に辺が張られているとみなして、すべての経路を全探索してしまっても間に合う問題になっている。

すべての重要魔法石に辺が張られているとみなしたときの、各魔法石へ辿り着くコストは以下のようになる。

f:id:takeg:20210201163034p:plain
重要魔法石の経路コスト表(例:重要魔法石3から重要魔法石0へはコスト2かかる)

この時点で「すべての重要魔法石を1回通る経路のうち最小のものは?」という問題に言い替わり、これは巡回セールスマン問題である。


巡回セールスマン問題はbitDPで解く。

巡回セールスマン問題のbitDPでの解き方は、以下の問題の解説動画がとても詳しい。

atcoder.jp

また、巡回セールスマン問題の練習問題として以下がある。

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DPL_2_A&lang=ja


各重要魔法石へ辿り着くコスト計算表は、BFSで計算する。すべての重要魔法石をスタート地点としたときのコスト計算表が必要なので、BFSはK回する。

BFSの一回あたりの計算量は O(N+M)と解説動画で言っていたが、あまりわかっていない。各頂点と各辺を1回ずつ走査するのでこの計算量か。

コーナーケース

  • 特になし

実装

Pythonだと間に合わなさそうだったのでC++で実装した。

#include <bits/stdc++.h>
using namespace std;


vector<int> BFS(int n, vector<vector<int>>& paths, int& N) {
    // BFSで宝石nから各宝石までの距離を求める
    vector<int> dist(N, INT_MAX); dist[n] = 0;
    set<int> visited; visited.insert(n);
    deque<int> que; que.push_back(n);
    while(!que.empty()) {
        int now = que.front();
        que.pop_front();

        for (int nx: paths[now]) {
            if (visited.count(nx)) continue;
            visited.insert(nx);
            dist[nx] = min(dist[nx], dist[now]+1);
            que.push_back(nx);
        }
    }
    return dist;
}

void solve(){
    int N, M; cin >> N >> M;
    vector<vector<int>> paths(N);
    for (int i=0; i<M; i++) {
        int A, B; cin >> A >> B; A--; B--;
        paths[A].push_back(B);
        paths[B].push_back(A);
    }

    int K; cin >> K;
    vector<int> Cs(K);
    for (int i=0; i<K; i++) {
        int C; cin >> C; C--;
        Cs[i] = C;
    }

    vector<vector<int>> dists;
    for (int i=0; i<K; i++) {
        dists.emplace_back(BFS(Cs[i], paths, N));  // 始点cからの最短距離を求める
    }

    // for (int i=0; i<K; i++) {
    //     cout << "FROM:" << Cs[i] << endl;
    //     for (int j=0; j<K; j++) {
    //         cout << Cs[j] << ":" << dists[i][Cs[j]] << endl;
    //     }
    // }

    // bitDP[S][i] := 既に訪れた重要宝石集合Sにおいて、現在の重要宝石がiのときの、最短距離
    vector<vector<int>> dp(1<<K, vector<int>(K, INT_MAX));
    for (int i=0; i<K; i++) {
        dp[0][i] = 0;
    }
    for (int i=0; i<K; i++) {
        for (int j=0; j<K; j++) {
            dp[(1<<i)|(1<<j)][j] = dists[i][Cs[j]];
        }
    }
    for (int S=1; S<(1<<K); S++) {
        for (int i=0; i<K; i++) {  // 現在の宝石i
            if (~S>>i&1) continue;  // iはまだ訪れていない
            for (int j=0; j<K; j++) {  // 次に行く宝石j
                if (S>>j&1) continue;  // jは既に訪れている
                int new_cost = dp[S][i]+dists[i][Cs[j]];
                if (new_cost<0) new_cost = INT_MAX;  // オーバーフロー対策
                dp[S|1<<j][j] = min(dp[S|1<<j][j], new_cost);
            }
        }
    }

    // 答え
    int ans = INT_MAX;
    for (int i=0; i<K; i++) {
        ans = min(ans, dp[pow(2,K)-1][i]);
    }
    if (ans == INT_MAX) {
        cout << -1 << endl;
    }
    else {
        cout << ans+1 << endl;
    }
}


int main(int argc, char const *argv[]){
    solve();
    return 0;
}

計算量は、bidDP部分が O(2^{K} K^{2})で、BFS部分が O(K (N+M)) 。全体としてはこれらを足した感じ。

bitDPの~S>>i&1~(S>>i&1)と書いていて5時間くらい溶かしたのは内緒だ。

ABC190 D問題

問題:AtCoder Beginner Contest 190: D - Staircase Sequences

解説

解説PDFまんまです。


初項a, 末項b とすると、数列は[a, a+1, ... , b-1, b]と表現できる

この数列の総和をSとすると、

 S = \frac{(a+b)(b-a+1)}{2}

今回の問題では  S = Nより、

 N = \frac{(a+b)(b-a+1)}{2}   (a \leq b)

両辺2倍すると、

 2N = (a+b)(b-a+1)   (a \leq b)・・・★

となる。これを満たす (a, b)の組の個数を求める問題である。


このとき、

 A=a+b

 B=b-a+1

とおくと★は、

 2N = AB

となる。

 A, B はそれぞれ正整数なので、 (A, B)の組はそれぞれ 2Nの約数といえる。

なので 2Nの約数を列挙して( O(\sqrt(2N))でできる)、 2Nの約数 A, Bについて、

 A = a + b・・・①

 B = b-a+1・・・②

が成立する個数を数えたい。

①より  a = A - b より、これを②に代入すると、

 B = b-(A-b)+1

式変形すると、

 B+A-1 = 2b

この式を満たすものを数えればよい。

つまり、 B+A-1が偶数の個数を数えれば良い。


例として、入力例1のN = 12のときを考える。

数列はそれぞれ

  • [12]
  • [3,4,5]
  • [-2, -1, ... ,4, 5]
  • [-11, -10, ..., 11, 12]

になるらしいが、それぞれ①と②にaとbを代入してみると、

  • a=12, b=12 ⇒ A=24, B=1
  • a=3, b=5 ⇒ A=8, B=3
  • a=-2, b=5 ⇒ A=3, B=8
  • a=-11, b=12 ⇒ A=1, B=24

となる。

A, Bはそれぞれ2N=24の約数になっていることがわかる。

コーナーケース

  • 特になし

実装

# -*- coding:utf-8 -*-

def diviser(n):
    """約数を列挙して返す"""
    ans = []
    i = 1
    while True:
        if i*i > n: break
        m = n%i
        if m==0:
            ans.append(i)
            if not n//i in ans:
                ans.append(n//i)
        i += 1
    return ans


def solve():
    N = int(input())
    ans = 0

    divs = diviser(2*N)
    divs.sort()

    for i in range(len(divs)):
        A, B = divs[-i-1], divs[i]
        if B>A: break

        """
        A = a+b
        B = b-a+1
        をそれぞれ満たすか?
        すなわち式変形して、
        B+A-1 = 2bを満たすか?
        つまりB+A-1は偶数か?
        """
        if (B+A-1)&1==0:
            ans += 2

    print(ans)


if __name__ == "__main__":
    solve()

ソート等含めた計算量は O(\sqrt(2N) + MlogM + M (Mは約数の個数))です 。