オーバーロードされたメンバ関数の有無による条件分岐

メンバ関数の有無による条件分岐をしようと思って調べました。

それ自体はできたのですが、メンバ関数オーバーロードされていると上手くいかなかったのでそれの解決策と実装例を載せたいと思います。

とりあえず、目標としてはイテレータを返り値とする 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_findvalueに何が入るのかというところから追って行ってください。

解決策

オーバーロードされたメンバ関数を指定するにはstatic_castで型を指定すればよいです。

static_cast<返り値(関数ポインタ*)(引数)>(目的のメンバ関数のアドレス)

のようにします。

実装例では

static_cast<省略::iterator (InnerContainer::*)(const T &)>(&InnerContainer::find)

としています。

ここで、イテレータを持たない型が入ってきたときのためにenable_ifhas_iterator<T>::valuetruefalseかを判別しています。

返り値の型がtrue_typeになっているcheck関数のテンプレート部分でエラーが起きると、返り値の型がfalse_typecheck関数に移ります。


基本的な説明はこちらの記事がとても分かりやすかったです。

あとがき

オーバーロードされたメンバ関数を指定するためには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法について

まえがき

atcoder.jp

ABC221 - D によって苦しめられたので imos 法を勉強しました。

値域の大きな imos 法についての名称が定まっていなかったのでとりあえず、動的 imos 法と名付けます。(動的セグメント木っぽさがあったように感じたので)

imos 法とは

imos 法は  [l, r) 区間にある値  v を追加したいというときに用いるものです。

  •  N, Q \leq 10^{5}
  •  l, r, v \leq N

上記の制約で  Q 個の区間  [l, r) に値  v を追加したいような場合を考えてみます。

まずは、長さ  N 程度の配列  A を用意して、 A[l ] += v  A[r ] -= v としておいて、あとで前から累積和をとっていくことで  A[i ] には  i の地点での値の合計が入っています

毎回、区間に値を一つずつ加算していく操作を愚直に行うと  O(NQ) ですが、imos 法を用いることで  O(N + Q) に改善できます。

値域が大きい場合

では、次のような例も考えてみます。

  •  N \leq 10^{9}
  •  Q \leq 10^{5}
  •  l, r, v \leq N

先ほどの例と同様に長さ  N 程度の配列を用意しようとした時点で、使用するメモリが多すぎて、足りなくなってしまいます。

そこで、連想配列を用いて区間の端点のみ扱うことで使用するメモリ量を節約しながら値を求められます。

区間  [l, r) を追加していき、前から累積和を取っていって、端点が持つ値を使用できます。

もしくは、区間を座標圧縮しておくことで前の例とほぼ同様に imos 法を適用できます。

実装

さて、ここまででお気持ちと理論は分かりました。

次に実装について細かい部分を考えていきます。

連想配列を使うよりも座標圧縮を行う方が簡単なので、そちらを使用します。

実際に注意して考えるべきなのは区間を扱う際の座標圧縮前後の値だけなので思ったよりは難しくありません。

※これより下部に書かれているコードは厳密ではないのでお気持ちだけ理解して下さい。

区間に値を挿入するパート

 l, r, v を受け取り、区間を扱う配列に追加します。

あとで座標圧縮をするため、値のみを扱う配列を別途用意して、区間の値を追加していきます。

// [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];
  }
}

値の受け取りパート

値の受け取りについてですが、

挿入された区間の最小値を  MIN
挿入された区間の最大値を  MAX

として、 [MIN, MAX) を値が変化する部分で区切った配列を返すことにします。

より具体的には、 [l_1, r_1) , [l_2, r_2), ... [l_Q, r_Q)  r_i = l_{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 &lt; (<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; }

あとがき

おつかれさまでしたー。

NITIC CTF2 参加記

NITIC CTF2に参加しました

36時間コンテストで9/5 12:00 - 9/6 24:00 (UTC+9)の間に開催されました

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を続けていこうかなと思います

水になった、色変記事

入水しました!!!(激遅)

f:id:matumoto_h:20210825131407p:plain

当時高校2年生の2019/2/3に始めてから水色になることができました。

茶色の期間が長く、伸び悩んでいた時期もありましたがなんとか水色に到達して感無量です。

なぜか、茶から緑になるまでよりも緑から水色になるまでの方が早かったりします。なぜ?????

それはともかく、最近のパフォーマンスが右肩上がりで嬉しいですね。このまま上がっていけたらさらに嬉しいです。

さて、せっかくの色変記事なので水色になるまでやったこととかをつらつらと書いていこうと思います。

やったこと

およそ1100ACはしました。若干ABCの問題が多いような気がします。

f:id:matumoto_h:20210825133941p:plain

学んだことの列挙

今まで学んだアルゴリズム/データ構造たちを列挙していきます。

  • データ構造
    • 累積和/二次元累積和
    • fenwick木(Binary Indexed Tree)+ $\alpha$(区間取得、二次元)
    • セグメント木/遅延セグメント木(ACL
    • UnionFind/重みつきUnionFind
    • mexset
    • dynamic conectivity
  • グラフ
    • ベルマンフォード法
    • ダイクストラ
    • ワーシャルフロイド法
    • オイラーツアー
    • BFS/DFS + $\alpha$ (グリッドグラフ上のBFS、01BFS)
    • 橋、間接点列挙(lowlink)
    • 強連結成分分解
    • トポロジカルソート
    • 最小共通祖先
    • 巡回セールスマン問題(これはbitDP)
  • 数学
  • 文字列
    • ローリングハッシュ/二次元ローリングハッシュ
  • その他
    • 二分探索
    • 座標圧縮
    • 転倒数
    • 多少の幾何(回転行列など)
    • 多少の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{引数の個数}を呼び出しているような感じだと思っています(よくわかっていない)

参考

stackoverflow.com