/** * author: rrrriki * created: 19.10.2024 14:18:23 */ //#define USE_ACL //#define USE_BOOST #if !__INCLUDE_LEVEL__ #include #include __FILE__ int main() { cin.tie(0); ios_base::sync_with_stdio(false); unsigned long long N, ans; cin >> N; ans = N * (N + 1) / 2; cout << ans << endl; return 0; } #else // clang-format off #ifdef USE_ACL #include using namespace atcoder; using mint = modint998244353; //using mint = modint1000000007; #endif #ifdef USE_BOOST #include #include #include #include #include #include #include #include #include #include #include using namespace boost::multiprecision; #endif #define ALL(x) x.begin(), x.end() #define YES cout << "Yes\n" #define NO cout << "No\n" using namespace std; #ifdef LOCAL #include "debug.h" #else #define dbg(...) 42 #endif using ll = long long; using vl = vector; /// コンテナの全出力 @tparam T コンテナの型 @param A コンテナ @param gap 区切り文字 template void out_c(T &A, string gap=" ") {auto itr = A.begin(); if (itr != A.end()) {cout << *itr; itr++;} while (itr != A.end()) {cout << gap << *itr; itr++;}cout << "\n"; return;} template void out_c_pairs(T &A, string gap_inside=" ", string gap_outside = " ") {auto itr = A.begin();if (itr != A.end()) {cout << itr->first << gap_inside << itr->second;itr++;}while (itr != A.end()) {cout << gap_outside << itr->first << gap_inside << itr->second;itr++;}cout << "\n";return;} /// べき乗を誤差なく計算する @param x 底 @param n 指数 @return x^n ll _pow(ll x, ll n) {if (n == 0) return 1; ll val = _pow(x, n / 2); val *= val; if (n & 1) val *= x; return val;} // マンハッタン距離 template T mnht(T a, T b, T c, T d) {return abs(a - c) + abs(b - d);} /// ランレングス圧縮 @param s 圧縮する文字列 @return 圧縮した文字列を格納したvector> vector> rle(const string &s){vector> vec;int cnt = 1; for(int i = 1; i < (int)s.size(); i++) {if(s[i] != s[i-1]){vec.emplace_back(s[i-1], cnt); cnt = 0;} cnt++;} vec.emplace_back(s.back(), cnt);return vec;} /// ランレングス圧縮 @tparam T 圧縮するvectorの型 @param v 圧縮するvector @return 圧縮したvectorを格納したvector> template vector> rle(const vector &v) {vector> vec;int cnt = 1; for(int i = 1; i < (int)v.size(); i++) {if(v[i] != v[i-1]){vec.emplace_back(v[i-1], cnt); cnt = 0;} cnt++;} vec.emplace_back(v.back(), cnt);return vec;} // 素数 bool is_prime(ll x){for (ll i=2; i*i<=x; i++){if(x%i==0)return false;}return true;} map prime_factor(ll n) {map ret; for(ll i=2; i*i <= n; i++) {while(n%i == 0) {ret[i]++; n /= i;}} if(n != 1) ret[n]=1;return ret;} vector sieve_of_era(ll n) {vector ret(n+1,true); ret[0]=false; ret[1]=false; for(ll i=2; i*i<=n; i++) {if(ret[i]) {for(ll j=i*2; j<=n; j+=i) {ret[j]=false;}}} return ret;} // 約数全列挙 vector divisor(ll n) {vector ret; for(ll i=1; i*i <= n; i++) {if(n%i == 0) {ret.push_back(i); if(i*i != n) ret.emplace_back(n/i);}} sort(ALL(ret)); return ret;} // 切り捨て、切り上げ、外側 inline constexpr ll ceil_div(const ll a, const ll b) {return (a + b - 1) / b - ((a + b - 1) % b < 0);} inline constexpr ll floor_div(const ll a, const ll b) {return a / b - (a % b < 0);} inline constexpr ll out_div(ll x, ll y) {ll d = x / y; return d * y == x ? d : ((x > 0) == (y > 0)) ? d + 1 : d - 1;} /// 組み合わせの全探索 @param k 組み合わせの要素数 template bool next_combination(const T first, const T last, int k) { const T subset = first + k; // empty container | k = 0 | k == n if (first == last || first == subset || last == subset) { return false; } T src = subset; while (first != src) { src--; if (*src < *(last - 1)) { T dest = subset; while (*src >= *dest) { dest++; } iter_swap(src, dest); rotate(src + 1, dest + 1, last); rotate(subset, subset + (last - dest) - 1, last); return true; } } // restore rotate(first, subset, last); return false; } /// 二次元行列の回転 @tparam T 行列の要素の型 @param matrix 行列 @return 回転した行列 template vector> rotate_matrix(const vector>& matrix) { int n = matrix.size(); int m = matrix[0].size(); vector> rotated(m, vector(n)); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { rotated[j][n - i - 1] = matrix[i][j]; } } return rotated; } /// string行列の回転 @param matrix 行列 @return 回転した行列 std::vector rotate_matrix(const std::vector& matrix) { int n = matrix.size(); int m = matrix[0].size(); std::vector rotated(m, std::string(n, ' ')); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { rotated[j][n - i - 1] = matrix[i][j]; } } return rotated; } // グラフ /** * @brief Edgeクラスはグラフのエッジ(辺)を表します。 * * @tparam T エッジの重みの型(デフォルトはint) */ template struct Edge { int from, to; // エッジの始点と終点 T cost; // エッジの重み int idx; // エッジのインデックス(オプション) // デフォルトコンストラクタ Edge() = default; // エッジをコストに基づいて比較するための演算子オーバーロード bool operator<(const Edge &other) const { return cost < other.cost; } bool operator>(const Edge& other) const { return cost > other.cost; } friend std::ostream& operator<<(std::ostream& os, const Edge& edge) { os << edge.to; return os; } // コンストラクタ Edge(int from, int to, T cost = 1, int idx = -1) : from(from), to(to), cost(cost), idx(idx) {} // エッジの終点をintとして取得するためのキャスト演算子 operator int() const { return to; } }; /** * @brief Graphクラスはグラフのデータ構造を表します。 * @tparam T エッジの重みの型(デフォルトはint) */ template struct Graph { vector>> g; // 各ノードから出ているエッジのリスト int es; // エッジの数 // デフォルトコンストラクタ Graph() = default; // ノード数nを指定するコンストラクタ explicit Graph(int n) : g(n), es(0) {} // グラフのサイズ(ノードの数)を返す size_t size() const { return g.size(); } // 有向エッジを追加する関数 void add_directed_edge(int from, int to, T cost = 1) { g[from].emplace_back(from, to, cost, es++); } // 無向エッジを追加する関数 void add_edge(int from, int to, T cost = 1) { g[from].emplace_back(from, to, cost, es); g[to].emplace_back(to, from, cost, es++); } /// @brief エッジを読み込む関数 @param M エッジの数 @param padding インデックスのオフセット @param weighted 重み付きかどうか @param directed 有向かどうか void read(int M, int padding = -1, bool weighted = false, bool directed = false) { for (int i = 0; i < M; i++) { int a, b; cin >> a >> b; a += padding; b += padding; T c = T(1); if (weighted) cin >> c; if (directed) add_directed_edge(a, b, c); else add_edge(a, b, c); } } // 演算子オーバーロード:インデックスによるエッジのリストへのアクセス inline vector> &operator[](const int &k) { return g[k]; } // 演算子オーバーロード(const版):インデックスによるエッジのリストへのアクセス inline const vector> &operator[](const int &k) const { return g[k]; } }; /// @brief エッジのリスト @tparam T エッジの重みの型 template using Edges = vector>; // ダイクストラ法 /** * @brief dijkstra関数はダイクストラ法を用いて最短経路を求める関数です。 * @tparam T エッジの重みの型 * @param g グラフ * @param s 始点 * @return vector 始点から各頂点への最短経路の長さ * @note 計算量はO((E+V)logV) */ template vector dijkstra(Graph &g, int s) { vector dist(g.size(), numeric_limits::max()); dist[s] = 0; priority_queue, vector>, greater>> pq; pq.emplace(0, s); while (!pq.empty()) { auto [d, v] = pq.top(); pq.pop(); if (dist[v] < d) continue; for (auto e : g[v]) { if (dist[e.to] > dist[v] + e.cost) { dist[e.to] = dist[v] + e.cost; pq.emplace(dist[e.to], e.to); } } } return dist; } #ifndef USE_ACL struct dsu { public: dsu() : _n(0) {} explicit dsu(int n) : _n(n), parent_or_size(n, -1) {} int merge(int a, int b) { assert(0 <= a && a < _n); assert(0 <= b && b < _n); int x = leader(a), y = leader(b); if (x == y) return x; if (-parent_or_size[x] < -parent_or_size[y]) std::swap(x, y); parent_or_size[x] += parent_or_size[y]; parent_or_size[y] = x; return x; } bool same(int a, int b) { assert(0 <= a && a < _n); assert(0 <= b && b < _n); return leader(a) == leader(b); } int leader(int a) { assert(0 <= a && a < _n); if (parent_or_size[a] < 0) return a; return parent_or_size[a] = leader(parent_or_size[a]); } int size(int a) { assert(0 <= a && a < _n); return -parent_or_size[leader(a)]; } std::vector> groups() { std::vector leader_buf(_n), group_size(_n); for (int i = 0; i < _n; i++) { leader_buf[i] = leader(i); group_size[leader_buf[i]]++; } std::vector> result(_n); for (int i = 0; i < _n; i++) { result[i].reserve(group_size[i]); } for (int i = 0; i < _n; i++) { result[leader_buf[i]].push_back(i); } result.erase( std::remove_if(result.begin(), result.end(), [&](const std::vector& v) { return v.empty(); }), result.end()); return result; } private: int _n; // root node: -1 * component size // otherwise: parent std::vector parent_or_size; }; #endif /** * @brief 重み付きUnionFind @tparam T 重みの型 * * 使い方 * * - UnionFindWithPotential uf(n):= n要素のUnionFindWithPotentialを宣言 * * - uf.merge(x, y, p):= P(x) = P(y) + p でマージ * * - uf.diff(x, y):= P(x) - P(y) を求める * * - uf.same(x, y):= xとyが同じ連結成分に属するかどうか * * - uf.potential(x):= xのポテンシャルを求める P(x) - P(root(x)) * * - uf.size(x):= xが属する連結成分のサイズを求める * * - uf.root(x):= xの根を求める * */ template struct UnionFindWithPotential { vector dat; // 親の番号 根の場合は-1 vector pot; // 親との差分 UnionFindWithPotential(int N) : dat(N, -1), pot(N, T()) {} /// @brief xの根を求める @return 根 int root(int x) { if (dat[x] < 0) return x; int r = root(dat[x]); pot[x] += pot[dat[x]]; return dat[x] = r; } /// @brief xのポテンシャルを求める @return P(x) - P(root(x)) T potential(int x) { root(x); return pot[x]; } bool same(int x, int y) { return root(x) == root(y); } /// @brief xとyのポテンシャルの差を求める @return P(x) - P(y) T diff(int x, int y) { return potential(x) - potential(y); } /// @brief P(x) = P(y) + p でマージ @param p ポテンシャルの差 @return マージできたかどうか bool merge(int x, int y, T p) { p += potential(y) - potential(x); x = root(x), y = root(y); if (x == y) return p == T(); if (dat[x] < dat[y]) swap(x, y), p = -p; dat[y] += dat[x]; dat[x] = y; pot[x] = p; return true; } /// @brief xが属する連結成分のサイズを求める @return xが属する連結成分のサイズ int size(int x) { return -dat[root(x)]; } }; /** * @brief krsukal関数はクラスカル法を用いて最小/最大全域木を求める関数です。 * @tparam T エッジの重みの型 * @param g グラフ * @param s 最小全域木を求める場合は"min"、最大全域木を求める場合は"max"を指定 * @return T 最小/最大全域木の重み * @note 計算量はO(ElogV) */ template T kruskal(Graph &g, string s = "min") { T res = 0; int n = g.size(); dsu dsu(n); Edges edges; for (int i = 0; i < n; i++) { for (auto e : g[i]) { edges.emplace_back(e); } } if (s == "max") sort(ALL(edges), greater>()); else sort(ALL(edges)); for (auto e : edges) { if (dsu.same(e.from, e.to)) continue; dsu.merge(e.from, e.to); res += e.cost; } return res; } /** * @brief CumulativeSum2Dは二次元累積和を計算するための構造体です。 * @tparam T 累積和の型 * * 使い方 * * - CumulativeSum2D cumsum(W, H):= W x H の二次元累積和を宣言 * * - add(x, y, z):= x, y に z を加算 * * - build():= 二次元累積和を構築 * * - query(sx, sy, gx, gy):= (sx, sy) から (gx, gy) までの和を計算 [sx, gx), [sy, gy) */ template struct CumulativeSum2D { vector > data; /// @brief W x H の二次元累積和を宣言 @param W 幅 @param H 高さ CumulativeSum2D(int W, int H) : data(W + 1, vector(H + 1, 0)) {} /// @brief x, y に z を加算 @param x x座標 @param y y座標 @param z 加算する値 void add(int x, int y, T z) { ++x, ++y; if (x >= (int)data.size() || y >= (int)data[0].size()) return; data[x][y] += z; } /// @brief 二次元累積和を構築 void build() { for (int i = 1; i < (int)data.size(); i++) { for (int j = 1; j < (int)data[i].size(); j++) { data[i][j] += data[i][j - 1] + data[i - 1][j] - data[i - 1][j - 1]; } } } /// @brief (sx, sy) から (gx, gy) までの和を計算 [sx, gx), [sy, gy) /// @param sx x座標の始点 @param sy y座標の始点 @param gx x座標の終点 @param gy y座標の終点 @note gxとgyは含まれない T query(int sx, int sy, int gx, int gy) const { return (data[gx][gy] - data[sx][gy] - data[gx][sy] + data[sx][sy]); } }; /** * @brief 座標圧縮を扱う汎用構造体 * @tparam T 座標の型 * * 使い方 * * - build(vector&... vectors):=座標圧縮を行い圧縮後の座標をtupleで返す * * - build(vector>& vectors):=二次元vector版 * * - compress(T value):=座標を圧縮する * * - decompress(int idx):=圧縮された座標を元に戻す * * - compress_vector(vector& vec):=vectorを圧縮する * * - decompress_vector(vector& vec):=圧縮されたvectorを元に戻す */ template struct CoordCompressor { unordered_map compressed_map; // 元の座標 -> 圧縮後の座標 unordered_map reverse_compressed_map; // 圧縮後の座標 -> 元の座標 /// @brief 座標圧縮を行う (可変長引数の対応) @param vectors 複数のvectorを受け取り圧縮 template auto build(Vectors&... vectors) { // 座標のsetを作成 set coords; (coords.insert(vectors.begin(), vectors.end()), ...); // 座標圧縮用のmapを作成 int idx = 0; for (const auto& coord : coords) { compressed_map[coord] = idx; reverse_compressed_map[idx] = coord; ++idx; } // 各vectorを圧縮、圧縮されたvectorのtupleを返す return make_tuple(compress_vector(vectors)...); } /// @brief vector> の圧縮 (二次元vector対応) vector> build(const vector>& vectors) { // 座標のsetを作成 set coords; for (const auto& vec : vectors) { coords.insert(vec.begin(), vec.end()); } // 座標圧縮用のmapを作成 int idx = 0; for (const auto& coord : coords) { compressed_map[coord] = idx; reverse_compressed_map[idx] = coord; ++idx; } // 二次元vectorを圧縮 vector> compressedVectors(vectors.size()); for (size_t i = 0; i < vectors.size(); ++i) { compressedVectors[i] = compress_vector(vectors[i]); } // 圧縮された二次元vectorを返す return compressedVectors; } /// @brief 値を圧縮する @param value 圧縮する値 @return 圧縮された値 int compress(const T& value) const { assert(compressed_map.count(value)); return compressed_map.at(value); } /// @brief 圧縮値を元に戻す @param idx 圧縮されたインデックス @return 元の値 T decompress(int idx) const { assert(reverse_compressed_map.count(idx)); return reverse_compressed_map.at(idx); } /// @brief vectorを圧縮する @param vec 圧縮するvector @return 圧縮されたvector vector compress_vector(const vector& vec) const { vector compressedVec(vec.size()); transform(vec.begin(), vec.end(), compressedVec.begin(), [&](const T& val) { return compress(val); }); return compressedVec; } /// @brief vectorを元に戻す @param vec 圧縮されたvector @return 元の値に戻されたvector vector decompress_vector(const vector& vec) const { vector decompressedVec(vec.size()); transform(vec.begin(), vec.end(), decompressedVec.begin(), [&](int val) { return decompress(val); }); return decompressedVec; } }; /** * @brief N-Queen問題を解くための構造体 * * 使い方 * * - NQueen nqueen(n):= n x n のチェス盤を宣言 * * - add_queen(x, y):= (x, y) にクイーンを置く * * - remove_queen(x, y):= (x, y) のクイーンを取り除く * * - is_valid(x, y):= (x, y) にクイーンを置けるかどうか * * - is_valid():= クイーンが全てのマスを利き筋に置いているかどうか * * - solve(x):= x行目以降のクイーンを置く * * - size():= チェス盤のサイズを返す * * - get_queens():= クイーンの位置をvector>に出力する * * - get_added_queens():= 追加したクイーンの位置をvector>に出力する * * - print(c, d):= 盤面を出力する c: クイーンの文字 d: 空白の文字 * */ struct NQueen { public: explicit NQueen(int n) : N(n) { assert(n > 0);} /// @brief クイーンを置く @param x x座標 @param y y座標 @return クイーンを置けたかどうか bool add_queen(int x, int y) { if (row.count(x) || col.count(y) || diag1.count(x + y) || diag2.count(x - y)) return false; assert(x < N && y < N); assert(x >= 0 && y >= 0); queens.emplace(x, y); row.insert(x); // x が同じ行にある col.insert(y); // y が同じ列にある diag1.insert(x + y); // x + y が同じ斜めの利き筋にある diag2.insert(x - y); // x - y が同じ斜めの利き筋にある return true; } /// @brief クイーンを取り除く @param x x座標 @param y y座標 @return クイーンを取り除けたかどうか bool remove_queen(int x, int y) { if (!row.count(x) || !col.count(y) || !diag1.count(x + y) || !diag2.count(x - y)) return false; assert(x < N && y < N); assert(x >= 0 && y >= 0); queens.erase({x, y}); if (added_queens.count({x, y})) added_queens.erase({x, y}); row.erase(x); col.erase(y); diag1.erase(x + y); diag2.erase(x - y); return true; } /// @brief x, yにクイーンを置けるかどうか @param x x座標 @param y y座標 @return クイーンを置けるかどうか bool is_valid(int x, int y) { return !row.count(x) && !col.count(y) && !diag1.count(x + y) && !diag2.count(x - y);} /// @brief クイーンが全てのマスを利き筋に置いているかどうか bool is_valid() { return (int)row.size() == N;} /// @brief x行目以降のクイーンを置く @return クイーンを置けたかどうか bool solve(int x = 0) { if (x == N) return true; if (is_valid()) return true; if (row.count(x)) return solve(x + 1); for (int y = 0; y < N; y++) { if (is_valid(x, y)) { add_queen(x, y); added_queens.emplace(x, y); if (solve(x + 1)) return true; remove_queen(x, y); } } return false; } /// @brief チェス盤のサイズを返す int size() { return N; } /// @brief クイーンの位置を出力する friend std::ostream& operator<<(std::ostream& os, const NQueen& nqueen) { for (auto [x, y] : nqueen.queens) os << x << " " << y << "\n"; return os; } /// @brief クイーンの位置をvector>に出力する vector > get_queens() { return vector >(queens.begin(), queens.end());} /// @brief 追加したクイーンの位置をvector>に出力する vector > get_added_queens() { return vector >(added_queens.begin(), added_queens.end());} /// @brief 盤面を出力する @param c クイーンの文字 @param d 空白の文字 @note デフォルトは 'Q' と '.' void print(char c = 'Q', char d = '.') { vector > board(N, vector(N, d)); for (auto [x, y] : queens) { board[x][y] = c; } for (auto& row : board) { for (auto& c : row) { cout << c; } cout << "\n"; } } private: int N; // チェス盤のサイズ unordered_set row, col, diag1, diag2; // それぞれの行、列、斜めの利き筋にクイーンがあるかどうか set > queens; // クイーンの位置 set > added_queens; // 追加したクイーンの位置 }; /** * @brief Rolling-Hash(ローリングハッシュ) * * 使い方 * * - RollingHash rh:= ローリングハッシュを宣言 * * - build(s):= 文字列sのハッシュ値を計算 * * - query(s, l, r):= 文字列sの[l, r)のハッシュ値を計算 * * - combine(h1, h2, h2len):= ハッシュ値h1と長さh2lenのハッシュ値h2を結合する * * - lcp(a, l1, r1, b, l2, r2):= ハッシュテーブルaの区間[l1,r1)と、ハッシュテーブルbの区間[l2,r2)の最長共通接頭辞の長さを求める * * @see https://qiita.com/keymoon/items/11fac5627672a6d6a9f6 * @see https://ei1333.github.io/library/string/rolling-hash.hpp */ struct RollingHash { static const uint64_t mod = (1ull << 61ull) - 1; using uint128_t = __uint128_t; const uint64_t base; vector< uint64_t > power; /// @brief 加算 @param a 加数 @param b 加数 @return 和 static inline uint64_t add(uint64_t a, uint64_t b) { if((a += b) >= mod) a -= mod; return a; } /// @brief 乗算 @param a 乗数 @param b 乗数 @return 積 static inline uint64_t mul(uint64_t a, uint64_t b) { uint128_t c = (uint128_t) a * b; return add(c >> 61, c & mod); } /// @brief 2^61-1 未満の乱数を生成する static inline uint64_t generate_base() { mt19937_64 mt(chrono::steady_clock::now().time_since_epoch().count()); uniform_int_distribution< uint64_t > rand(1, RollingHash::mod - 1); return rand(mt); } /// @brief ハッシュテーブルのサイズを拡張する @param sz 拡張するサイズ inline void expand(size_t sz) { if(power.size() < sz + 1) { int pre_sz = (int) power.size(); power.resize(sz + 1); for(int i = pre_sz - 1; i < (int)sz; i++) { power[i + 1] = mul(power[i], base); } } } explicit RollingHash(uint64_t base = generate_base()) : base(base), power{1} {} /// @brief 文字列sのハッシュ値を計算する @param s 文字列 @return ハッシュ値 vector< uint64_t > build(const string &s) const { int sz = s.size(); vector< uint64_t > hashed(sz + 1); for(int i = 0; i < sz; i++) { hashed[i + 1] = add(mul(hashed[i], base), s[i]); } return hashed; } /// @brief ベクタsのハッシュ値を計算する @tparam T ベクタの型 @param s ベクタ @return ハッシュ値 template< typename T > vector< uint64_t > build(const vector< T > &s) const { int sz = s.size(); vector< uint64_t > hashed(sz + 1); for(int i = 0; i < sz; i++) { hashed[i + 1] = add(mul(hashed[i], base), s[i]); } return hashed; } /// @brief 文字列sの[l, r)のハッシュ値を計算する @param s 文字列 @param l 左端 @param r 右端 @return ハッシュ値 uint64_t query(const vector< uint64_t > &s, int l, int r) { expand(r - l); return add(s[r], mod - mul(s[l], power[r - l])); } /// @brief ハッシュ値h1とハッシュ値h2を結合する @param h1 ハッシュ値1 @param h2 ハッシュ値2 @param h2len ハッシュ値2の長さ @return 結合されたハッシュ値 uint64_t combine(uint64_t h1, uint64_t h2, size_t h2len) { expand(h2len); return add(mul(h1, power[h2len]), h2); } /// @brief ハッシュテーブルaの区間[l1,r1)と、ハッシュテーブルbの区間[l2,r2)の最長共通接頭辞の長さを求める @param a ハッシュテーブルa @param l1 左端 @param r1 右端 @param b ハッシュテーブルb @param l2 左端 @param r2 右端 @return 最長共通接頭辞の長さ int lcp(const vector< uint64_t > &a, int l1, int r1, const vector< uint64_t > &b, int l2, int r2) { int len = min(r1 - l1, r2 - l2); int low = 0, high = len + 1; while(high - low > 1) { int mid = (low + high) / 2; if(query(a, l1, l1 + mid) == query(b, l2, l2 + mid)) low = mid; else high = mid; } return low; } }; /** * @brief K-Shortest-PathをYen’s Algorithm により求める関数 * @tparam T グラフの重みの型 @param g グラフ @param s 始点 @param t 終点 @param k 最短経路の数 * * 使い方 * * - k_shotest_path(g, s, t, k): 重み付き有向グラフ g の頂点 s から t へのパスのうち, * 昇順 k 個のパスの長さとそのパスの辺番号の列を返す(パスの個数が k 個に満たないとき全てを返す) * * @return vector>> 最短経路の長さと経路 @note 計算量はO(kV((E+V)logV)) */ template vector>> k_shortest_path(const Graph &g, int s, int t, int k) { assert(s != t); int N = (int)g.size(); int M = 0; for (int i = 0; i < N; i++) M += (int)g[i].size(); vector latte(M), malta(M); vector cost(M); for (int i = 0; i < N; i++) { for (auto &e : g[i]) { latte[e.idx] = i; malta[e.idx] = e.to; cost[e.idx] = e.cost; } } const auto INF = numeric_limits::max(); vector dame(M, -1); int timestamp = 0; // dijkstra auto shortest_path = [&](vector &dist, vector &from, vector &id, int st) { using Pi = pair; priority_queue, greater<>> que; que.emplace(dist[st], st); while (!que.empty()) { T cost; int idx; tie(cost, idx) = que.top(); que.pop(); if (dist[idx] < cost) continue; if (idx == t) return; for (auto &e : g[idx]) { auto next_cost = cost + e.cost; if (dist[e.to] <= next_cost) continue; if (dame[e.idx] == timestamp) continue; dist[e.to] = next_cost; from[e.to] = idx; id[e.to] = e.idx; que.emplace(dist[e.to], e.to); } } }; auto restore = [](const vector &es, const vector &vs, int from, int to) { vector tap; while (to != from) { tap.emplace_back(es[to]); to = vs[to]; } reverse(begin(tap), end(tap)); return tap; }; vector dist(g.size(), INF); vector from(g.size(), -1), id(g.size(), -1); dist[s] = 0; shortest_path(dist, from, id, s); if (dist[t] == INF) return {}; vector > > A; set > > B; A.emplace_back(dist[t], restore(id, from, s, t)); for (int i = 1; i < k; i++) { dist.assign(g.size(), INF); from.assign(g.size(), -1); id.assign(g.size(), -1); dist[s] = 0; vector candidate(A.size()); iota(begin(candidate), end(candidate), 0); auto &last_path = A.back().second; int cur = s; for (int j = 0; j < last_path.size(); j++) { for (auto &k : candidate) { if (j < A[k].second.size()) dame[A[k].second[j]] = timestamp; } vector dist2{dist}; vector from2{from}, id2{id}; shortest_path(dist2, from2, id2, cur); ++timestamp; if (dist2[t] != INF) { auto path = restore(id2, from2, s, t); bool ok = true; for (auto &p : candidate) { if (path == A[p].second) { ok = false; break; } } if (ok) B.emplace(dist2[t], path); } vector accept; for (auto &k : candidate) { if (j < A[k].second.size() && A[k].second[j] == last_path[j]) { accept.emplace_back(k); } } dist[malta[last_path[j]]] = dist[latte[last_path[j]]] + cost[last_path[j]]; from[malta[last_path[j]]] = latte[last_path[j]]; id[malta[last_path[j]]] = last_path[j]; cur = malta[last_path[j]]; candidate = move(accept); } if (B.size()) { A.emplace_back(*B.begin()); B.erase(B.begin()); } } return A; } // --------------------------------------- // ----- segment trees by @rrrrikiOW ----- // --------------------------------------- // ----- Based on AtCoder Library -------- // -------------- VER.1.1.0 -------------- // ----- Last Update: 2024/03/03 --------- // --------------------------------------- /// @brief 2の冪に切り上げる @param n 数 @return 2の冪 int ceil_pow2(int n) { int x = 0; while ((1U << x) < (unsigned int)(n)) x++; return x; } #ifndef USE_ACL /// @brief セグメント木 /// @tparam S セグメント木の型 @tparam op セグメント木の演算 @tparam e セグメント木の単位元 template struct segtree { public: segtree() : segtree(0) {} explicit segtree(int n) : segtree(std::vector(n, e())) {} explicit segtree(const std::vector& v) : _n(int(v.size())) { log = ceil_pow2(_n); size = 1 << log; d = std::vector(2 * size, e()); for (int i = 0; i < _n; i++) d[size + i] = v[i]; for (int i = size - 1; i >= 1; i--) { update(i); } } /// @brief 0-indexed で k 番目の要素を x に変更する O(logN) void set(int p, S x) { assert(0 <= p && p < _n); p += size; d[p] = x; for (int i = 1; i <= log; i++) update(p >> i); } /// @brief 0-indexed で k 番目の要素を取得する O(logN) S get(int p) const { assert(0 <= p && p < _n); return d[p + size]; } /// @brief op(a[l], ..., a[r - 1]) を、モノイドの性質を満たしていると仮定して計算します。 l=r のときは e() を返します。 O(logN) S prod(int l, int r) const { assert(0 <= l && l <= r && r <= _n); S sml = e(), smr = e(); l += size; r += size; while (l < r) { if (l & 1) sml = op(sml, d[l++]); if (r & 1) smr = op(d[--r], smr); l >>= 1; r >>= 1; } return op(sml, smr); } /// @brief op(a[0], ..., a[n - 1]) を、モノイドの性質を満たしていると仮定して計算します O(1) S all_prod() const { return d[1]; } template int max_right(int l) const { return max_right(l, [](S x) { return f(x); }); } template int max_right(int l, F f) const { assert(0 <= l && l <= _n); assert(f(e())); if (l == _n) return _n; l += size; S sm = e(); do { while (l % 2 == 0) l >>= 1; if (!f(op(sm, d[l]))) { while (l < size) { l = (2 * l); if (f(op(sm, d[l]))) { sm = op(sm, d[l]); l++; } } return l - size; } sm = op(sm, d[l]); l++; } while ((l & -l) != l); return _n; } template int min_left(int r) const { return min_left(r, [](S x) { return f(x); }); } template int min_left(int r, F f) const { assert(0 <= r && r <= _n); assert(f(e())); if (r == 0) return 0; r += size; S sm = e(); do { r--; while (r > 1 && (r % 2)) r >>= 1; if (!f(op(d[r], sm))) { while (r < size) { r = (2 * r + 1); if (f(op(d[r], sm))) { sm = op(d[r], sm); r--; } } return r + 1 - size; } sm = op(d[r], sm); } while ((r & -r) != r); return 0; } private: int _n, size, log; std::vector d; void update(int k) { d[k] = op(d[2 * k], d[2 * k + 1]); } }; /// @brief 遅延セグメント木 /// @tparam S セグメント木の型 @tparam op セグメント木の演算 @tparam e セグメント木の単位元 /// @tparam F 作用素の型 @tparam mapping 作用素の演算 @tparam composition 作用素の合成 @tparam id 作用素の単位元 template struct lazy_segtree { public: lazy_segtree() : lazy_segtree(0) {} explicit lazy_segtree(int n) : lazy_segtree(std::vector(n, e())) {} explicit lazy_segtree(const std::vector& v) : _n(int(v.size())) { log = ceil_pow2(_n); size = 1 << log; d = std::vector(2 * size, e()); lz = std::vector(size, id()); for (int i = 0; i < _n; i++) d[size + i] = v[i]; for (int i = size - 1; i >= 1; i--) { update(i); } } /// @brief 0-indexed で k 番目の要素を x に変更する O(logN) void set(int p, S x) { assert(0 <= p && p < _n); p += size; for (int i = log; i >= 1; i--) push(p >> i); d[p] = x; for (int i = 1; i <= log; i++) update(p >> i); } /// @brief 0-indexed で k 番目の要素を取得する O(logN) S get(int p) { assert(0 <= p && p < _n); p += size; for (int i = log; i >= 1; i--) push(p >> i); return d[p]; } /// @brief op(a[l], ..., a[r - 1]) を、モノイドの性質を満たしていると仮定して計算します。 l=r のときは e() を返します。 O(logN) S prod(int l, int r) { assert(0 <= l && l <= r && r <= _n); if (l == r) return e(); l += size; r += size; for (int i = log; i >= 1; i--) { if (((l >> i) << i) != l) push(l >> i); if (((r >> i) << i) != r) push((r - 1) >> i); } S sml = e(), smr = e(); while (l < r) { if (l & 1) sml = op(sml, d[l++]); if (r & 1) smr = op(d[--r], smr); l >>= 1; r >>= 1; } return op(sml, smr); } /// @brief op(a[0], ..., a[n - 1]) を、モノイドの性質を満たしていると仮定して計算します O(1) S all_prod() { return d[1]; } /// @brief a[p] = f(a[p]) void apply(int p, F f) { assert(0 <= p && p < _n); p += size; for (int i = log; i >= 1; i--) push(p >> i); d[p] = mapping(f, d[p]); for (int i = 1; i <= log; i++) update(p >> i); } /// @brief [l, r) の要素に f を作用させます O(logN) void apply(int l, int r, F f) { assert(0 <= l && l <= r && r <= _n); if (l == r) return; l += size; r += size; for (int i = log; i >= 1; i--) { if (((l >> i) << i) != l) push(l >> i); if (((r >> i) << i) != r) push((r - 1) >> i); } { int l2 = l, r2 = r; while (l < r) { if (l & 1) all_apply(l++, f); if (r & 1) all_apply(--r, f); l >>= 1; r >>= 1; } l = l2; r = r2; } for (int i = 1; i <= log; i++) { if (((l >> i) << i) != l) update(l >> i); if (((r >> i) << i) != r) update((r - 1) >> i); } } template int max_right(int l) { return max_right(l, [](S x) { return g(x); }); } template int max_right(int l, G g) { assert(0 <= l && l <= _n); assert(g(e())); if (l == _n) return _n; l += size; for (int i = log; i >= 1; i--) push(l >> i); S sm = e(); do { while (l % 2 == 0) l >>= 1; if (!g(op(sm, d[l]))) { while (l < size) { push(l); l = (2 * l); if (g(op(sm, d[l]))) { sm = op(sm, d[l]); l++; } } return l - size; } sm = op(sm, d[l]); l++; } while ((l & -l) != l); return _n; } template int min_left(int r) { return min_left(r, [](S x) { return g(x); }); } template int min_left(int r, G g) { assert(0 <= r && r <= _n); assert(g(e())); if (r == 0) return 0; r += size; for (int i = log; i >= 1; i--) push((r - 1) >> i); S sm = e(); do { r--; while (r > 1 && (r % 2)) r >>= 1; if (!g(op(d[r], sm))) { while (r < size) { push(r); r = (2 * r + 1); if (g(op(d[r], sm))) { sm = op(d[r], sm); r--; } } return r + 1 - size; } sm = op(d[r], sm); } while ((r & -r) != r); return 0; } private: int _n, size, log; std::vector d; std::vector lz; void update(int k) { d[k] = op(d[2 * k], d[2 * k + 1]); } void all_apply(int k, F f) { d[k] = mapping(f, d[k]); if (k < size) lz[k] = composition(f, lz[k]); } void push(int k) { all_apply(2 * k, lz[k]); all_apply(2 * k + 1, lz[k]); lz[k] = id(); } }; #endif /// @brief 双対セグメント木 @tparam T セグメント木の型 @tparam composition セグメント木のマージ関数 @tparam id セグメント木の単位元 /// @fn apply 区間に作用を適用する @fn get 位置pの値を取得する template struct dual_segtree { public: /// @brief セグメント木を初期化する @param n サイズ explicit dual_segtree(int n) : dual_segtree(std::vector(n, id())) {} /// @brief セグメント木を初期化する @param v vector型の配列 explicit dual_segtree(const std::vector &v) : _n(int(v.size())) { log = ceil_pow2(_n); size = 1 << log; lz = std::vector(2 * size, id()); for (int i = 0; i < _n; i++) lz[size + i] = v[i]; } /// @brief [l, r) の要素に f を作用させます O(logN) @param l 左端 @param r 右端 @param f 作用素 void apply(int l, int r, F f) { assert(0 <= l && l <= r && r <= _n); if (l == r) return; l += size; r += size; for (int i = log; i >= 1; i--) { // 遅延評価 if (((l >> i) << i) != l) push(l >> i); // lがiの倍数でない場合は、lを親に移動 if (((r >> i) << i) != r) push((r - 1) >> i); // rがiの倍数でない場合は、rを親に移動 } while (l < r) { if (l & 1) all_apply(l++, f); // lが奇数の場合は、lに作用を適用してからlをインクリメント if (r & 1) all_apply(--r, f); // rが奇数の場合は、rをデクリメントしてからrに作用を適用 l >>= 1; // lを親に移動 r >>= 1; // rを親に移動 } } /// @brief 位置pの値を取得する @param p 位置 F get(int p) { assert(0 <= p && p < _n); p += size; for (int i = log; i >= 1; i--) push(p >> i); return lz[p]; } private: int _n, size, log; std::vector lz; /// @brief 作用素を遅延評価する @param i 位置 @param f 作用素 void all_apply(int i, F f) { lz[i] = composition(f, lz[i]); } /// @brief 作用素を遅延評価する @param i 位置 void push(int i) { assert(i < size); all_apply(2 * i, lz[i]); all_apply(2 * i + 1, lz[i]); lz[i] = id(); } }; // ----- segment trees by @rrrrikiOW ----- /** * @author @rrrrikiOW * @brief EulerTourを行い、部分木・パスクエリ・LCAを解く * @tparam E_T 辺の重みの型 @tparam V_T 頂点の重みの型 */ template struct EulerTour { private: /// @brief 辺の重みを管理 struct PE{ E_T value; // 辺の重み int rate; // 0 寄与しない 1 加算する -1 減算 PE(E_T value, int rate) : value(value), rate(rate) {} PE() : value(0), rate(0) {} E_T val() { return value*rate; } }; /// @brief 頂点の重みを管理 struct PV{ V_T value; // 頂点の重み int rate; // 0 寄与しない 1 加算する -1 減算 PV(V_T value, int rate) : value(value), rate(rate) {} PV() : value(0), rate(0) {} V_T val() { return value*rate; } }; bool build_lca_flag = false; bool build_seg_edge_flag[2] = {false, false}; bool build_seg_vert_flag[2] = {false, false}; vector vert_weight; // 頂点の重み unordered_map> edge_to_index; vector edge_weight_tour, edge_weight_tour_minus; // 辺の重み vector vert_weight_tour, vert_weight_tour_minus; // 頂点の重み vector> depth; // (depth, node) using S = pair; static S op_lca(S a, S b) { return min(a, b); } // depthの最小値を求める static S e_lca() { return {1e9,1e9}; } // e: 単位元 segtree seg_lca; // LCAを求めるセグメント木 static PE op_edge(PE a, PE b) { return PE(a.value*a.rate + b.value*b.rate, 1); } static PE e_edge() { return PE(0, 0); } // e: 単位元 static PE mapping_edge(E_T f, PE x) { return (x.rate == 0) ? x : PE(x.value + f, x.rate); } // mapping: 作用素 f に対して x に作用させる関数 static E_T composition_edge(E_T f, E_T g) { return f+g; } // composition: 作用素 f, g を合成する関数 static E_T id_edge() { return 0; } // id: 作用素の単位元 lazy_segtree seg_edge0, seg_edge1; // 辺の合計を管理するセグメント木 static PV op_vert(PV a, PV b) { return PV(a.value*a.rate + b.value*b.rate, 1); } static PV e_vert() { return PV(0, 0); } // e: 単位元 static PV mapping_vert(V_T f, PV x) { return (x.rate == 0) ? x : PV(x.value + f, x.rate); } // mapping: 作用素 f に対して x に作用させる関数 static V_T composition_vert(V_T f, V_T g) { return f+g; } // composition: 作用素 f, g を合成する関数 static V_T id_vert() { return 0; } // id: 作用素の単位元 lazy_segtree seg_vert0, seg_vert1; // 点の合計を管理するセグメント木 /// @brief lcaを構築する void build_lca() { seg_lca = segtree(depth); build_lca_flag = true; } /// @brief seg_edgeを構築する @param ind 0 部分木 or 1 パスクエリ void build_seg_edge(int ind) { build_seg_edge_flag[ind] = true; if (ind == 0) seg_edge0 = lazy_segtree(edge_weight_tour); else seg_edge1 = lazy_segtree(edge_weight_tour_minus); } /// @brief seg_vertを構築する @param ind 0 部分木 or 1 パスクエリ void build_seg_vert(int ind) { build_seg_vert_flag[ind] = true; if (ind == 0) seg_vert0 = lazy_segtree(vert_weight_tour); else seg_vert1 = lazy_segtree(vert_weight_tour_minus); } public: vector in, out; // コンストラクタ EulerTour(Graph &g, int root=0, vector vert_w=vector()) : in(g.size()), out(g.size()){ if (vert_w.size() == 0) vert_weight = vector(g.size(), 1); else vert_weight = vert_w; int idx = 0; edge_weight_tour.reserve(2 * g.size()); edge_weight_tour_minus.reserve(2 * g.size()); vert_weight_tour.reserve(2 * g.size()); vert_weight_tour_minus.reserve(2 * g.size()); edge_weight_tour.emplace_back(PE(0, 0)); edge_weight_tour_minus.emplace_back(PE(0, 0)); vert_weight_tour.emplace_back(PV(vert_weight[root], 1)); vert_weight_tour_minus.emplace_back(PV(vert_weight[root], 1)); depth.emplace_back(0, root); function dfs = [&](int v, int p, int d) { in[v] = idx++; for (auto e : g[v]) { if (e.to == p) continue; depth.emplace_back(d + 1, e.to); edge_weight_tour.emplace_back(PE(e.cost, 1)); edge_weight_tour_minus.emplace_back(PE(e.cost, 1)); vert_weight_tour.emplace_back(PV(vert_weight[e.to], 1)); vert_weight_tour_minus.emplace_back(PV(vert_weight[e.to], 1)); edge_to_index[min(v,(int)e.to) *(ll)1e10 + max(v,(int)e.to)] = {idx, -1}; dfs(e.to, v, d + 1); edge_to_index[min(v,(int)e.to) *(ll)1e10 + max(v,(int)e.to)].second = idx++; depth.emplace_back(d, v); edge_weight_tour.emplace_back(PE(0,0)); edge_weight_tour_minus.emplace_back(PE(e.cost, -1)); vert_weight_tour.emplace_back(PV(0, 0)); vert_weight_tour_minus.emplace_back(PV(vert_weight[e.to], -1)); } out[v] = idx; }; dfs(root, -1, 0); } /// @brief lcaを求める @param u ノードu @param v ノードv @return lca int lca(int u, int v) { if (!build_lca_flag) build_lca(); return seg_lca.prod(min(in[u], in[v]), max(out[u], out[v])).second; } /// @brief 辺のパスクエリ @param u ノードu @param v ノードv @return 辺のコスト E_T query_edge(int u, int v) { // [0, in[u]+1) + [0, in[v]+1) - 2* [0, in[lca(u,v)+1)] if (!build_seg_edge_flag[1]) build_seg_edge(1); if (!build_lca_flag) build_lca(); return (seg_edge1.prod(0, in[u]+1)).val() + (seg_edge1.prod(0, in[v]+1)).val() - 2*(seg_edge1.prod(0, in[lca(u,v)]+1)).val(); } /// @brief 頂点のパスクエリ @param u ノードu @param v ノードv @return 頂点のコスト V_T query_vert(int u, int v) { // [0, in[u]+1) + [0, in[v]+1) - 2* [0, in[lca(u,v)+1)] if (!build_seg_vert_flag[1]) build_seg_vert(1); if (!build_lca_flag) build_lca(); return (seg_vert1.prod(0, in[u]+1)).val() + (seg_vert1.prod(0, in[v]+1)).val() - 2*(seg_vert1.prod(0, in[lca(u,v)]+1)).val(); } /// @brief 部分木の辺コストの合計を求める @param u ノードu @return 部分木の辺のコスト E_T query_subtree_edge(int u) { if (!build_seg_edge_flag[0]) build_seg_edge(0); return (seg_edge0.prod(in[u]+1, out[u])).val(); } /// @brief 部分木の頂点コストの合計を求める @param u ノードu @return 部分木の頂点のコスト V_T query_subtree_vert(int u) { if (!build_seg_vert_flag[0]) build_seg_vert(0); return (seg_vert0.prod(in[u], out[u])).val(); } /// @brief 辺のコストを更新する @param u ノードu @param v ノードv @param x 更新する値 @param mode 0 部分木 or 1 パスクエリ or 2 両方 void update_edge(int u, int v, E_T x, int mode=1) { if (u>v) swap(u,v); if (mode != 1) { if (!build_seg_edge_flag[0]) build_seg_edge(0); seg_edge0.set(edge_to_index[(ll)u *1e10 + v].first, PE(x, 1)); } if (mode != 0) { if (!build_seg_edge_flag[1]) build_seg_edge(1); seg_edge1.set(edge_to_index[(ll)u *1e10 + v].first, PE(x, 1)); seg_edge1.set(edge_to_index[(ll)u *1e10 + v].second, PE(x, -1)); } } /// @brief 辺のコストを加算する @param u ノードu @param v ノードv @param x 加算する値 @param mode 0 部分木 or 1 パスクエリ or 2 両方 void add_cost_edge(int u, int v, E_T x, int mode=1) { if (u>v) swap(u,v); if (mode != 1) { if (!build_seg_edge_flag[0]) build_seg_edge(0); PE target = seg_edge0.get(edge_to_index[(ll)u *1e10 + v].first); seg_edge0.set(edge_to_index[(ll)u *1e10 + v].first, target.value + x, target.rate); } if (mode != 0) { if (!build_seg_edge_flag[1]) build_seg_edge(1); PE target = seg_edge1.get(edge_to_index[(ll)u *1e10 + v].first); PE target2 = seg_edge1.get(edge_to_index[(ll)u *1e10 + v].second); seg_edge1.set(edge_to_index[(ll)u *1e10 + v].first, target.value + x, target.rate); seg_edge1.set(edge_to_index[(ll)u *1e10 + v].second, target2.value + x, target2.rate); } } /// @brief 頂点のコストを更新する @param u ノードu @param x 更新する値 @param mode 0 部分木 or 1 パスクエリ or 2 両方 void update_vert(int u, V_T x, int mode=1) { if (mode != 1) { if (!build_seg_vert_flag[0]) build_seg_vert(0); seg_vert0.set(in[u], PV(x, 1)); } if (mode != 0) { if (!build_seg_vert_flag[1]) build_seg_vert(1); seg_vert1.set(in[u], PV(x, 1)); seg_vert1.set(out[u], PV(x, -1)); } } /// @brief 頂点のコストを加算する @param u ノードu @param x 加算する値 @param mode 0 部分木 or 1 パスクエリ or 2 両方 void add_cost_vert(int u, V_T x, int mode=1) { if (mode != 1) { if (!build_seg_vert_flag[0]) build_seg_vert(0); PV target = seg_vert0.get(in[u]); seg_vert0.set(in[u], target.value + x, target.rate); } if (mode != 0) { if (!build_seg_vert_flag[1]) build_seg_vert(1); PV target = seg_vert1.get(in[u]); PV target2 = seg_vert1.get(out[u]); seg_vert1.set(in[u], target.value + x, target.rate); seg_vert1.set(out[u], target2.value + x, target2.rate); } } /// @brief 部分木の全頂点にコストを加算する @param u ノードu @param x 加算する値 @param mode 0 部分木 or 1 パスクエリ or 2 両方 void add_cost_subtree_vert(int u, V_T x, int mode=1) { if (mode != 1) { if (!build_seg_vert_flag[0]) build_seg_vert(0); seg_vert0.apply(in[u], out[u], x); } if (mode != 0) { if (!build_seg_vert_flag[1]) build_seg_vert(1); seg_vert1.apply(in[u], out[u], x); } } /// @brief 部分木の全辺にコストを加算する @param u ノードu @param x 加算する値 @param mode 0 部分木 or 1 パスクエリ or 2 両方 void add_cost_subtree_edge(int u, E_T x, int mode=1) { if (mode != 1) { if (!build_seg_edge_flag[0]) build_seg_edge(0); seg_edge0.apply(in[u]+1, out[u], x); } if (mode != 0) { if (!build_seg_edge_flag[1]) build_seg_edge(1); seg_edge1.apply(in[u]+1, out[u], x); } } /// @brief depthを取得する @param u ノードu @return depth int get_depth(int u) { return depth[in[u]].first; } /// @brief 頂点のコストを取得する @param u ノードu @param mode 0 部分木 or 1 パスクエリ V_T get_vert(int u, int mode=1) { if (mode == 0) { if (!build_seg_vert_flag[0]) build_seg_vert(0); return seg_vert0.get(in[u]).val(); } else { if (!build_seg_vert_flag[1]) build_seg_vert(1); return seg_vert1.get(in[u]).val(); } } /// @brief 辺のコストを取得する @param u ノードu @param v ノードv @param mode 0 部分木 or 1 パスクエリ E_T get_edge(int u, int v, int mode=1) { if (u>v) swap(u,v); if (mode == 0) { if (!build_seg_edge_flag[0]) build_seg_edge(0); return seg_edge0.get(edge_to_index[(ll)u *1e10 + v].first).val(); } else { if (!build_seg_edge_flag[1]) build_seg_edge(1); return seg_edge1.get(edge_to_index[(ll)u *1e10 + v].first).val(); } } }; /// @brief 乱択平衡二分木(基底クラス) template struct RBSTBase { using Ptr = Node *; template inline Ptr my_new(Args... args) { return new Node(args...); } inline void my_del(Ptr t) { delete t; } /// @brief 空の木を返す。 O(1) inline Ptr make_tree() const { return nullptr; } // for avoiding memory leak, activate below /* using Ptr = shared_ptr; template inline Ptr my_new(Args... args) { return make_shared(args...); } inline void my_del(Ptr t) {} Ptr make_tree() {return Ptr();} */ /// @brief tの大きさを返す。 O(1) int size(Ptr t) const { return count(t); } /// @brief lとrをマージして新たに出来た木のポインタを返す。 O(logN) Ptr merge(Ptr l, Ptr r) { if (!l || !r) return l ? l : r; if (int((rng() * (l->cnt + r->cnt)) >> 32) < l->cnt) { push(l); l->r = merge(l->r, r); return update(l); } else { push(r); r->l = merge(l, r->l); return update(r); } } ///@brief tを[0, k)と[k, |t|)の二つの木に分割する。 O(logN) pair split(Ptr t, int k) { if (!t) return {nullptr, nullptr}; push(t); if (k <= count(t->l)) { auto s = split(t->l, k); t->l = s.second; return {s.first, update(t)}; } else { auto s = split(t->r, k - count(t->l) - 1); t->r = s.first; return {update(t), s.second}; } } Ptr build(int l, int r, const vector &v) { if (l + 1 == r) return my_new(v[l]); int m = (l + r) >> 1; Ptr pm = my_new(v[m]); if (l < m) pm->l = build(l, m, v); if (m + 1 < r) pm->r = build(m + 1, r, v); return update(pm); } /// @brief 列vを初期値とした新たな木を作る。 O(N) Ptr build(const vector &v) { return build(0, (int)v.size(), v); } /// @brief tのk番目にNode(args...)を挿入する。 O(logN) template void insert(Ptr &t, int k, const Args &... args) { auto x = split(t, k); t = merge(merge(x.first, my_new(args...)), x.second); } /// @brief tのk番目のノードを削除する。 O(logN) void erase(Ptr &t, int k) { auto x = split(t, k); auto y = split(x.second, 1); my_del(y.first); t = merge(x.first, y.second); } protected: static uint64_t rng() { static uint64_t x_ = 88172645463325252ULL; return x_ ^= x_ << 7, x_ ^= x_ >> 9, x_ & 0xFFFFFFFFull; } inline int count(const Ptr t) const { return t ? t->cnt : 0; } virtual void push(Ptr) = 0; virtual Ptr update(Ptr) = 0; }; /// @brief RBSTのノード キー(値)、部分木の合計、遅延伝搬用の変数、左右の子ノードへのポインタ、部分木のサイズ、反転フラグ template struct LazyReversibleRBSTNode { typename RBSTBase::Ptr l, r; T key, sum; E lazy; int cnt; bool rev; LazyReversibleRBSTNode(const T &t = T(), const E &e = E()) : l(), r(), key(t), sum(t), lazy(e), cnt(1), rev(false) {} }; /// @brief 遅延伝搬反転可能乱択平衡二分木 /// @see https://nyaannyaan.github.io/library/rbst/lazy-reversible-rbst.hpp.html template struct LazyReversibleRBST : RBSTBase> { using Node = LazyReversibleRBSTNode; using base = RBSTBase>; using base::merge; using base::split; using typename base::Ptr; LazyReversibleRBST() = default; /// @brief tを反転する。 O(1) void toggle(Ptr t) { if(!t) return; swap(t->l, t->r); t->sum = ts(t->sum); t->rev ^= true; } /// @brief fold(t, a, b) : [a, b) の和を求める O(logN) T fold(Ptr &t, int a, int b) { auto x = split(t, a); auto y = split(x.second, b - a); auto ret = sum(y.first); t = merge(x.first, merge(y.first, y.second)); return ret; } /// @brief reverse(t, a, b) : [a, b) を反転する O(logN) void reverse(Ptr &t, int a, int b) { auto x = split(t, a); auto y = split(x.second, b - a); toggle(y.first); t = merge(x.first, merge(y.first, y.second)); } /// @brief apply(t, a, b, e) : [a, b) に e を作用させる O(logN) void apply(Ptr &t, int a, int b, const E &e) { auto x = split(t, a); auto y = split(x.second, b - a); propagate(y.first, e); t = merge(x.first, merge(y.first, y.second)); } protected: inline T sum(const Ptr t) const { return t ? t->sum : T(); } Ptr update(Ptr t) override { push(t); t->cnt = 1; t->sum = t->key; if (t->l) t->cnt += t->l->cnt, t->sum = f(t->l->sum, t->sum); if (t->r) t->cnt += t->r->cnt, t->sum = f(t->sum, t->r->sum); return t; } void push(Ptr t) override { if (t->rev) { if (t->l) toggle(t->l); if (t->r) toggle(t->r); t->rev = false; } if (t->lazy != E()) { if (t->l) propagate(t->l, t->lazy); if (t->r) propagate(t->r, t->lazy); t->lazy = E(); } } void propagate(Ptr t, const E &x) { t->lazy = h(t->lazy, x); t->key = g(t->key, x); t->sum = g(t->sum, x); } }; /** * @brief 二部グラフ構造体 * * 使い方 * * - BipartiteGraph(g):= gの二部グラフを作成 * * - is_bipartitte():= 二部グラフかどうかを返す * * - operator\[\](i):= i番目の頂点の色を返す * */ struct BipartiteGraph : dsu { /** * @brief コンストラクタ * @tparam GraphType グラフの型 * @param g グラフのインスタンス * * グラフを受け取り、二部グラフの判定を行います。 * 結果は `is_bipartite` に格納されます。 */ template BipartiteGraph(const GraphType& g) : dsu(g.size() * 2), color(g.size() * 2, -1), colored(false) { is_bipartite_flag = bipartite(g); } /** * @brief 二部グラフかどうかを返す関数 * @return 二部グラフであれば true、そうでなければ false */ bool is_bipartite() { return is_bipartite_flag; } /** * @brief 二部グラフの彩色を行う関数 * @return 彩色が可能であれば true、そうでなければ false * * グラフが二部グラフである場合に、各連結成分に対して色を割り当てます。 * この関数は内部で使用され、`operator[]` から呼び出されます。 */ bool bipartite_graph_coloring() { int n = color.size() / 2; for (int i = 0; i < n; ++i) { int a = leader(i); int b = leader(i + n); if (a == b) return false; if (color[a] == -1) { color[a] = 0; color[b] = 1; } } return true; } /** * @brief 指定した頂点の色を取得する演算子オーバーロード * @param i 頂点のインデックス * @return 頂点の色(0または1) * * 頂点の色が未割り当ての場合、彩色を行う */ int operator[](int i) { if (!colored) { colored = true; bipartite_graph_coloring(); } return color[leader(i)]; } private: /// 各頂点の色を格納するベクター(0または1) vector color; /// グラフが二部グラフかどうかを保持するフラグ bool is_bipartite_flag; /// 彩色済みかどうかを保持するフラグ bool colored; /** * @brief 二部グラフかどうかを判定する関数 * @tparam GraphType グラフの型 * @param g グラフのインスタンス * @return 二部グラフであれば true、そうでなければ false */ template bool bipartite(const GraphType& g) { int n = g.size(); for (int i = 0; i < n; ++i) { for (const auto& e : g[i]) { merge(e.from, e.to + n); merge(e.to, e.from + n); } } for (int v = 0; v < n; ++v) { if (same(v, v + n)) { return false; } } return true; } }; // clang-format on #endif /* ******* 神龜雖壽 ******* ******* 猶有竟時 ******* ******* 騰蛇乘霧 ******* ******* 終爲土灰 ******* ******* 老驥伏櫪 ******* ******* 志在千里 ******* ******* 烈士暮年 ******* ******* 壯心不已 ******* ******* 盈縮之期 ******* ******* 不但在天 ******* ******* 養怡之福 ******* ******* 可得永年 ******* ******* 幸甚至哉 ******* ******* 歌以詠志 ******* */