オーバーロードされたメンバ関数の有無による条件分岐
メンバ関数の有無による条件分岐をしようと思って調べました。
それ自体はできたのですが、メンバ関数がオーバーロードされていると上手くいかなかったのでそれの解決策と実装例を載せたいと思います。
とりあえず、目標としてはイテレータを返り値とする find
メンバ関数の有無で分岐するような contains
関数を作りたいと思います。
結論
結論としては下記のような実装例になりました。
#include <bits/stdc++.h>
と using namespace std;
を使用しています(すいません)。
namespace helper { template <typename T> class has_iterator { template <typename Container> static true_type test(typename Container::iterator *); template <typename Container> static false_type test(...); public: static const bool value = decltype(test<T>(0))::value; }; template <typename Container, typename T> class has_find { template <typename InnerContainer, int dummy = (static_cast<typename enable_if<has_iterator<InnerContainer>::value, InnerContainer>::type::iterator (InnerContainer::*)(const T &)>(&InnerContainer::find), 0)> static true_type check(InnerContainer *); static false_type check(...); static Container *container; public: static const bool value = decltype(check(container))::value; }; } // namespace helper template <typename Container, typename T> bool contains(const Container &container, const T &x) { if constexpr (helper::has_find<Container, T>::value) { return container.find(x) != end(container); } else { return find(begin(container), end(container), x) != end(container); } }
#include <bits/stdc++.h> using namespace std; namespace helper { template <typename T> class has_iterator { template <typename Container> static true_type test(typename Container::iterator *); template <typename Container> static false_type test(...); public: static const bool value = decltype(test<T>(0))::value; }; template <typename Container, typename T> class has_find { template <typename InnerContainer, int dummy = (static_cast<typename enable_if<has_iterator<InnerContainer>::value, InnerContainer>::type::iterator (InnerContainer::*)(const T &)>(&InnerContainer::find), 0)> static true_type check(InnerContainer *); static false_type check(...); static Container *container; public: static const bool value = decltype(check(container))::value; }; } // namespace helper template <typename Container, typename T> bool contains(const Container &container, const T &x) { if constexpr (helper::has_find<Container, T>::value) { return container.find(x) != end(container); } else { return find(begin(container), end(container), x) != end(container); } } int main() { cout << boolalpha; { cout << "set-------------" << endl; set<int> s; s.insert(10); cout << contains(s, 10) << endl; cout << contains(s, 3) << endl; } { cout << "map-------------" << endl; map<int, int> mp; mp[2] = 3; cout << contains(mp, 3) << endl; cout << contains(mp, 2) << endl; } { cout << "vector-------------" << endl; vector<int> vs = { 1, 4, 3 }; cout << contains(vs, 5) << endl; cout << contains(vs, 4) << endl; cout << contains(vs, 1) << endl; } { // expect : string uses std::find cout << "string-------------" << endl; string s = "341"; cout << contains(s, '5') << endl; cout << contains(s, '4') << endl; cout << contains(s, '1') << endl; } }
実装例を目で追うときははhas_find
のvalue
に何が入るのかというところから追って行ってください。
解決策
オーバーロードされたメンバ関数を指定するにはstatic_cast
で型を指定すればよいです。
static_cast<返り値(関数ポインタ*)(引数)>(目的のメンバ関数のアドレス)
のようにします。
実装例では
static_cast<省略::iterator (InnerContainer::*)(const T &)>(&InnerContainer::find)
としています。
ここで、イテレータを持たない型が入ってきたときのためにenable_if
でhas_iterator<T>::value
がtrue
かfalse
かを判別しています。
返り値の型がtrue_type
になっているcheck
関数のテンプレート部分でエラーが起きると、返り値の型がfalse_type
のcheck
関数に移ります。
基本的な説明はこちらの記事がとても分かりやすかったです。
あとがき
オーバーロードされたメンバ関数を指定するためにはstatic_cast
で型を指定する必要があり、メンバ関数の有無を判定するものは全てには対応させられないのではないかと思います。(マクロならできるんですかね?)
オイラーのファイ関数とその別種について
まえがき
ACPC 2021 day2 E で出たオイラーのファイ関数に似た何かについてまとめました。
既出ではない...?(知らないだけかも)
本題
オイラーのファイ関数は 1 から N までの自然数について N と互いに素なものの個数を求めるものです。
ここに X を追加して X から N までの自然数について X と互いに素なものの個数を求めるものが今回の本題です。
求め方
オイラーのφ関数は公式が存在し、
$ \phi(n) = n \displaystyle \prod_{i=1}^k \left(1- \dfrac{1}{p_i} \right )ϕ(n)=n $
を計算することによって求められます。
お気持ちとしては素因数の倍数の個数を引いていっているだけです。
C++ で書くと以下のようになります。
計算量は $ O (\sqrt N ) $ です。
long long euler_phi(long long n) { long long res = n; for ( long long i = 2; i * i <= n; i++ ) { if ( n % i == 0 ) { res -= res / i; while ( n % i == 0 ) n /= i; } } if ( n > 1 ) res -= res / n; return res; }
別種の方
次に、先ほどの X から N までの自然数について X と互いに素なものの個数を求めていきます。
基本方針としては、X から N までの X - N + 1 個の中から X の素因数の倍数の個数を引いていく形です。( X と互いに素ということは X のどの素因数の倍数でもない)
最初に、X と X が互いに素となるのは X が 1 のときだけなので、 X + 1 から N までの X - N 個の中から選ぶようにします。
とりあえず、X を素因数分解し、 $ p^{e_1} _ {1}, p^{e_2} _ {2}, ... , p^{e_k} _ {k} $ と k 個の素因数に分解できたとします。
これら k 個の素因数 $ p _ {1} , p _ {2} , ... , p _ {k} $ について、
- 1 種類選んで掛け合わせた数の N - X 個の中にある倍数の個数 ( $ _ k \mathrm{C}_1 $ 個存在する )
- 2 種類選んで掛け合わせた数の N - X 個の中にある倍数の個数 ( $ _ k \mathrm{C}_2 $ 個存在する )
︙
- k 種類選んで掛け合わせた数の N - X 個の中にある倍数の個数 ( $ _ k \mathrm{C}_k $ 個存在する )
について見ていきます。
例として、X = 6, N = 12 の場合を見ていきます。
7, 8, 9, 10, 11, 12 の 6 個が対象の範囲です。
6 = 2 $ \times $ 3
と素因数分解して、
2 の倍数である 8, 10, 12 の 3 つを削ります。
3 の倍数である 9, 12 の 2 つを削ります。
ここで、 2 の倍数でもあり 3 の倍数でもある( 6 の倍数 ) 12 を削りすぎてしまったので
6 の倍数である 12 の 1 つを足します。
そうすると、式としては以下のようになります。
$ \frac{12-6}{2} + \frac{12-6}{3} + \frac{12-6}{6} $
このように、素因数を奇数個使う場合は引き、偶数個使う場合は足すというような包除原理をして求めることができます。
1 から k 種類選んで掛け合わせた数というのは
$ \displaystyle \sum_{i=1}^{k} {} _ {k} C _ { i } = 2 ^ {k} $
通りあるので、素因数使う使わないの bit 全探索をして足し引きしていけば求められます。
よって、計算量が $ O( \sqrt{X} 2 ^ {k} ) $ 程度となり求めることができました。
以下、実装例です。
long long alternative_euler_phi(long long x, long long n) { if ( x == 1 ) return n; auto ps = prime_factor(x); ps.erase(unique(ps.begin(), ps.end()), ps.end()); int k = ps.size(); long long res = n - x; for ( int i = 1; i < (1 << k); i++ ) { long long prod = 1; int cnt = 0; for ( int j = 0; j < k; j++ ) { if ( i >> j & 1 ) { prod *= ps[j]; cnt++; } } if ( cnt % 2 ) { res += (n - x) / prod; } else { res -= (n - x) / prod; } } return res; }
おまけ
N を固定して X が 1 から N まであるテーブルとして求める場合は愚直に $ O(N \sqrt{N} 2 ^ {k} ) $ とするよりも高速にすることができます。
事前に 1 から N までの平方因子を持たない数についての素因数の個数を求めておきます。
そうすると平方因子を持たない整数側(ある数 X の約数)から包除原理をしていくことができ、計算量が $ O(N \log N ) $ 程度になります。
動的なimos法について
まえがき
ABC221 - D によって苦しめられたので imos 法を勉強しました。
値域の大きな imos 法についての名称が定まっていなかったのでとりあえず、動的 imos 法と名付けます。(動的セグメント木っぽさがあったように感じたので)
imos 法とは
imos 法は の区間にある値 を追加したいというときに用いるものです。
上記の制約で 個の区間 に値 を追加したいような場合を考えてみます。
まずは、長さ 程度の配列 を用意して、、としておいて、あとで前から累積和をとっていくことで には の地点での値の合計が入っています
毎回、区間に値を一つずつ加算していく操作を愚直に行うと ですが、imos 法を用いることで に改善できます。
値域が大きい場合
では、次のような例も考えてみます。
先ほどの例と同様に長さ 程度の配列を用意しようとした時点で、使用するメモリが多すぎて、足りなくなってしまいます。
そこで、連想配列を用いて区間の端点のみ扱うことで使用するメモリ量を節約しながら値を求められます。
区間 を追加していき、前から累積和を取っていって、端点が持つ値を使用できます。
もしくは、区間を座標圧縮しておくことで前の例とほぼ同様に imos 法を適用できます。
実装
さて、ここまででお気持ちと理論は分かりました。
次に実装について細かい部分を考えていきます。
連想配列を使うよりも座標圧縮を行う方が簡単なので、そちらを使用します。
実際に注意して考えるべきなのは区間を扱う際の座標圧縮前後の値だけなので思ったよりは難しくありません。
※これより下部に書かれているコードは厳密ではないのでお気持ちだけ理解して下さい。
区間に値を挿入するパート
を受け取り、区間を扱う配列に追加します。
あとで座標圧縮をするため、値のみを扱う配列を別途用意して、区間の値を追加していきます。
// [l, r) void add(T l, T r, T v) { sections.emplace_back(l, r, v); xs.emplace_back(l); xs.emplace_back(r); }
累積和パート
区間の値を座標圧縮し、累積和を格納する配列に端点とその値を追加していきます。
追加し終えたら、累積和を行います。
void build() { sort(xs.begin(), xs.end()); xs.erase(unique(xs.begin(), xs.end()), xs.end()); imos.assign(xs.size(), 0); for ( auto [l, r, v] : sections ) { l = lower_bound(xs.begin(), xs.end(), l) - xs.begin(); r = lower_bound(xs.begin(), xs.end(), r) - xs.begin(); imos[l] += v; imos[r] -= v; } for ( int i = 1; i < (int)imos.size(); i++ ) { imos[i] += imos[i - 1]; } }
値の受け取りパート
値の受け取りについてですが、
として、 を値が変化する部分で区切った配列を返すことにします。
より具体的には、 ( を満たす)となるような区間と値のペアが入った配列を返します。
// vector<[l,r), value> vector<pair<pair<T, T>, T>> section_values() { vector<pair<pair<T, T>, T>> res(xs.size() - 1); for ( int i = 0; i < (int)xs.size() - 1; i++ ) { T l = xs[i]; T r = xs[i + 1]; T v = imos[i]; res[i] = make_pair(make_pair(l, r), v); } return res; }
3 つのパートをまとめると、以下のようになります。
template <typename T> struct dynamic_imos { vector<T> xs, imos; vector<tuple<T, T, T>> sections; dynamic_imos() {}// [l, r) void add(T l, T r, T v) { sections.emplace_back(l, r, v); xs.emplace_back(l); xs.emplace_back(r); }
void build() { sort(xs.begin(), xs.end()); xs.erase(unique(xs.begin(), xs.end()), xs.end()); imos.assign(xs.size(), 0);
<span class="synStatement">for</span> ( <span class="synType">auto</span> [l, r, v] : sections ) { l = lower_bound(xs.begin(), xs.end(), l) - xs.begin(); r = lower_bound(xs.begin(), xs.end(), r) - xs.begin(); imos[l] += v; imos[r] -= v; } <span class="synStatement">for</span> ( <span class="synType">int</span> i = <span class="synConstant">1</span>; i < (<span class="synType">int</span>)imos.size(); i++ ) { imos[i] += imos[i - <span class="synConstant">1</span>]; }
}
// vector<[l,r), value> vector<pair<pair<T, T>, T>> section_values() { vector<pair<pair<T, T>, T>> res(xs.size() - 1); for ( int i = 0; i < (int)xs.size() - 1; i++ ) { T l = xs[i]; T r = xs[i + 1]; T v = imos[i]; res[i] = make_pair(make_pair(l, r), v); } return res; } };
使用例はこんな感じです。
int main() { int n; cin >> n;dynamic_imos<int> dimos;
vector<P> ab(n); for ( auto &[a, b] : ab ) { cin >> a >> b; dimos.add(a, a + b, 1); }
dimos.build();
auto secs = dimos.section_values();
vector<int> ans(n + 1, 0); for ( auto [section, value] : secs ) { auto [l, r] = section; ans[value] += r - l; }
range(i, 1, n) cout << ans[i] << ' '; cout << ans[n] << endl; }
あとがき
おつかれさまでしたー。
assert するときのメモ
c++ で assert をする際についでにメッセージを表示したいみたいなとき
assert(("x == 0", x != 0));
NITIC CTF2 参加記
NITIC CTF2に参加しました
36時間コンテストで9/5 12:00 - 9/6 24:00 (UTC+9)の間に開催されました
#nitic_ctf_2
— フレキシブル基板 (@FPC_COMMUNITY) 2021年9月4日
本日昼の12時から開催です!
プログラミング未経験の中学生でも解ける問題から、CTF上級者でも難しいと思われる問題まで用意しております〜。
参加登録はDiscordサーバに入ってもらい、お知らせチャンネルに問題サーバのリンクがあるのでそこからどうぞ!https://t.co/NsemRCPzqD
1201点を獲得し、89位でした
CTFはほとんど初心者なのですが、解いた問題のwrite upを書いていこうと思います(頭痛が痛いみたいになってるような)
write up
アンケート
日頃の行いを回答しました
Caesar Cipher - 100
fdhvdu
が与えられるのでこれを復号します
シーザー暗号は知っていたので、与えられた文字列を1~26文字ずらすようなプログラムを書いてその中からflagっぽいものを探しました
ord_xor - 300
flagとenc_sample.pyという各ファイルが与えられるのでflagを復号します
コードを見てみると、入力が環境変数から、出力がファイルへ書き出すようなものだったので標準入出力に書き直しました
その後、とりあえず入力にflagに書いてあった文字列を入力すると答えが得られたのでACしました
そこまで深くコードを読み込まずになんとなくでやってしまった感がいなめないですね
web_meta - 100
HTMLファイルが与えられます
開発者の気持ちになれというメッセージがついていたので、とりあえずデベロッパーツールを開いたらflagがありました
Excel - 100
Excelファイルが与えられます
どこかのセルにあるんだろうなぁみたいなことをぼんやり思ったので、Excelの検索窓にniticと打ち込んだらflagがありました
protected - 200
challと名付けられたファイルが与えられます
とりあえず、file
コマンドを打ちました
chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=34e0075bd7d601785c00143e049a88c6ec927e50, for GNU/Linux 3.2.0, not stripped
これだけ読んでもよくわからなかったので、cat
コマンドで見てみると読めない文字列が多くありました
ただ、その中にも読める文字列がいくつかあったのでstrings -a chall
と打つとPASSWORDやらmainやらprintfなどの文字列が見受けられたのでエスパーして./chall
実行してみるといい感じにPASSWORDを聞かれました
stringsコマンドでPASSWORD:の次にあった文字列を入力するとフラグが得られました
long flag - 200
あるURLが与えられます
URLに飛ぶと、横線が2本あってその間にフラグがありそうなページが表示されます
とりあえず、デベロッパーツールを開くとフラグが<span>
タグで一文字ずつ囲われたのがあったのでタグを取り消して得られました
ちょうどつい最近、
https://matumoto1234.github.io/erase-html-tag/
を作ったのでこれを使って取り消しました
image_conv - 200
PNG画像が与えられます
おもむろに透過してみるとなにやら、文字が書いてあるようでした
適当に塗りつぶしてみるとフラグが書かれていることが分かりました
あとがき
初心者でも十分に楽しめるだけの問題が多くあって、とても楽しかったです
出られそうな大会を見つけたら、ゆるくCTFを続けていこうかなと思います
水になった、色変記事
入水しました!!!(激遅)
当時高校2年生の2019/2/3に始めてから水色になることができました。
茶色の期間が長く、伸び悩んでいた時期もありましたがなんとか水色に到達して感無量です。
なぜか、茶から緑になるまでよりも緑から水色になるまでの方が早かったりします。なぜ?????
それはともかく、最近のパフォーマンスが右肩上がりで嬉しいですね。このまま上がっていけたらさらに嬉しいです。
さて、せっかくの色変記事なので水色になるまでやったこととかをつらつらと書いていこうと思います。
やったこと
およそ1100ACはしました。若干ABCの問題が多いような気がします。
学んだことの列挙
今まで学んだアルゴリズム/データ構造たちを列挙していきます。
- データ構造
- グラフ
- 数学
- 文字列
- ローリングハッシュ/二次元ローリングハッシュ
- その他
- 二分探索
- 座標圧縮
- 転倒数
- 多少の幾何(回転行列など)
- 多少のDP(区間DPなど)
こんなところでしょうか。 なんだか学んだことの自慢をしているようになってしまった感は多少あります。
データ構造についての知識は他の1200付近の方よりも多い気はします。(おそらく)
心持ち
その代わりに算数的な直感や数学的な考察が苦手で、なるべく知識を使って脊髄で問題を殴れる(・_・)-=つ ようにするように心がけてきました。
知ってる問題が出たら、はいはいはいはいみたいなことを言いながらライブラリを貼ってAC!みたいなことが多い気がします。
他にもPythonをABCのA、B、C問題をACできる程度にはやっておくとPythonの便利な関数、counterとかがあってそれをc++に持ってきたりもしました。
お勧めしたいこと
スニペット
僕はVSCode勢なのですが、スニペットを使うとAtCoderライフが快適になります。
テンプレートやよく使うアルゴリズムをスニペットにしておくとかなりうれしいです。脊髄から取り出してそのまま殴れます。
強い人のライブラリをパクる
パクりましょう。
貼るだけ。
強い人の提出をパクる
いい感じのテンプレートを置いてくれています。
パクりましょう。
強い人のTwitterを見る
もし自分と同じ考えだったときにテンションが上がります。
拡張機能を入れる
ユーザースクリプトなどを入れてAtCoderライフをより快適にできます。
コンテスト中でのパフォーマンス予測や、どのレートの人が何%どの問題を解いているかなどが分かるようになります。
自分の環境を整える
スニペットと似たようなニュアンスです。
デバッグなどが早いとうれしくなります。
自分の場合はデバッグ用のマクロを組んでいるのですが、かなり助かっています。
あとがき
ここまで見てくださってありがとうございました。 今後も精進に励んでいきたいと思います。
青になります!!
快適なrangeマクロ
僕は普段c++を使って競技プログラミングをしているのですが、その中でrep
マクロを使う機会が多くあります。ふと、pythonのrangeのように引数によってその効果が変わるようなrep
マクロに近い何かが欲しいなと思ったので記事を参考に作ってみました
#define GET_MACRO(_1,_2,_3,_4,NAME,...) NAME #define range(...) GET_MACRO(__VA_ARGS__, range4, range3, range2)(__VA_ARGS__) #define range2(i, r) for ( int i = 0; i < (int)(r); (i) += 1) #define range3(i, l, r) for ( int i = (int)(l); i < (int)(r); (i) += 1) #define range4(i, l, r, inc) for ( int i = (int)(l); i < (int)(r); (i) += (inc)) #define rrange(...) GET_MACRO(__VA_ARGS__, rrange4, rrange3, rrange2)(__VA_ARGS__) #define rrange2(i, r) for ( int i = (int)(r) - 1; i >= 0; (i) -= 1) #define rrange3(i, l, r) for ( int i = (int)(r) - 1; i >= (int)(l); (i) -= 1) #define rrange4(i, l, r, inc) for ( int i = (int)(r) - 1; i >= (int)(l); (i) -= inc)
#include <iostream> using namespace std; #define GET_MACRO(_1,_2,_3,_4,NAME,...) NAME #define range(...) GET_MACRO(__VA_ARGS__, range4, range3, range2)(__VA_ARGS__) #define range2(i, r) for ( int i = 0; i < (int)(r); (i) += 1) #define range3(i, l, r) for ( int i = (int)(l); i < (int)(r); (i) += 1) #define range4(i, l, r, inc) for ( int i = (int)(l); i < (int)(r); (i) += (inc)) #define rrange(...) GET_MACRO(__VA_ARGS__, rrange4, rrange3, rrange2)(__VA_ARGS__) #define rrange2(i, r) for ( int i = (int)(r) - 1; i >= 0; (i) -= 1) #define rrange3(i, l, r) for ( int i = (int)(r) - 1; i >= (int)(l); (i) -= 1) #define rrange4(i, l, r, inc) for ( int i = (int)(r) - 1; i >= (int)(l); (i) -= inc) int main() { int l, r, inc; cin >> l >> r >> inc; range(i, r) { cout << i << ' '; } cout << endl; range(i, l, r) { cout << i << ' '; } cout << endl; range(i, l, r, inc) { cout << i << ' '; } cout << endl; rrange(i, r) { cout << i << ' '; } cout << endl; rrange(i, l, r) { cout << i << ' '; } cout << endl; rrange(i, l, r, inc) { cout << i << ' '; } cout << endl; }
これを試しに10 20 2
(10以上20未満を2ずつ)で実行すると以下のようになります
10 20 2 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 10 11 12 13 14 15 16 17 18 19 10 12 14 16 18 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 19 18 17 16 15 14 13 12 11 10 19 17 15 13 11
[l, r)
の半開区間なのでこれで正しいと思います
満足がいく結果となりました
仕組みとしては、引数の数をGET_MACROでrange{引数の個数}を呼び出しているような感じだと思っています(よくわかっていない)