結果
問題 | No.529 帰省ラッシュ |
ユーザー | tsutaj |
提出日時 | 2018-03-21 03:27:44 |
言語 | C++14 (gcc 12.3.0 + boost 1.83.0) |
結果 |
AC
|
実行時間 | 820 ms / 4,500 ms |
コード長 | 18,180 bytes |
コンパイル時間 | 2,294 ms |
コンパイル使用メモリ | 158,032 KB |
実行使用メモリ | 83,712 KB |
最終ジャッジ日時 | 2024-06-24 18:36:50 |
合計ジャッジ時間 | 11,150 ms |
ジャッジサーバーID (参考情報) |
judge3 / judge4 |
(要ログイン)
テストケース
テストケース表示入力 | 結果 | 実行時間 実行使用メモリ |
---|---|---|
testcase_00 | AC | 2 ms
5,248 KB |
testcase_01 | AC | 2 ms
5,376 KB |
testcase_02 | AC | 2 ms
5,376 KB |
testcase_03 | AC | 2 ms
5,376 KB |
testcase_04 | AC | 6 ms
5,376 KB |
testcase_05 | AC | 6 ms
5,376 KB |
testcase_06 | AC | 6 ms
5,376 KB |
testcase_07 | AC | 6 ms
5,376 KB |
testcase_08 | AC | 398 ms
50,168 KB |
testcase_09 | AC | 416 ms
51,232 KB |
testcase_10 | AC | 526 ms
67,292 KB |
testcase_11 | AC | 522 ms
68,012 KB |
testcase_12 | AC | 453 ms
41,600 KB |
testcase_13 | AC | 620 ms
83,712 KB |
testcase_14 | AC | 342 ms
51,328 KB |
testcase_15 | AC | 745 ms
74,576 KB |
testcase_16 | AC | 745 ms
74,676 KB |
testcase_17 | AC | 809 ms
75,484 KB |
testcase_18 | AC | 820 ms
75,240 KB |
testcase_19 | AC | 809 ms
72,468 KB |
ソースコード
// 基本テンプレート #include <iostream> #include <iomanip> #include <cstdio> #include <string> #include <cstring> #include <deque> #include <list> #include <queue> #include <stack> #include <vector> #include <utility> #include <algorithm> #include <map> #include <set> #include <complex> #include <cmath> #include <limits> #include <cfloat> #include <climits> #include <ctime> #include <cassert> #include <numeric> #include <fstream> #include <functional> using namespace std; #define rep(i,a,n) for(int (i)=(a); (i)<(n); (i)++) #define repq(i,a,n) for(int (i)=(a); (i)<=(n); (i)++) #define repr(i,a,n) for(int (i)=(a); (i)>=(n); (i)--) #define debug(...) fprintf(stderr, __VA_ARGS__) #define int long long int template<typename T> void chmax(T &a, T b) {a = max(a, b);} template<typename T> void chmin(T &a, T b) {a = min(a, b);} template<typename T> void chadd(T &a, T b) {a = a + b;} typedef pair<int, int> pii; typedef long long ll; int dx[] = {0, 0, 1, -1}; int dy[] = {1, -1, 0, 0}; const ll INF = 1001001001001001LL; const ll MOD = 1000000007LL; // vector 版 (HL 分解に載せるときとかに使おう) template<typename T> struct lazysegtree { // ノード、単位元 T I; vector<T> node, lazy; vector<bool> need_upd; int SIZE; // オペレーション (update, query の 2 つが必要?) // update function は範囲を指定する形にしよう // upd_f(X, Y, l, r) -> 範囲が [l, r) であるようなノード X に Y を反映! // lazy について update するときは範囲を 1 にしないとバグります T (*upd_f)(T, T, int, int), (*qry_f)(T, T); // 演算子と単位元をセットし、全ての node と lazy を単位元で初期化 lazysegtree(int seg_size, T (*op1)(T, T, int, int), T (*op2)(T, T), T X) { upd_f = op1; qry_f = op2; I = X; SIZE = 1; while(SIZE < seg_size) SIZE *= 2; node = vector<T> (2*SIZE, I ); lazy = vector<T> (2*SIZE, I ); need_upd = vector<bool>(2*SIZE, false); } // 配列 vec の値で初期化 void init(vector<T> vec) { int N = (int)vec.size(); for(int i=0; i<N; i++) { node[SIZE-1+i] = vec[i]; } for(int i=SIZE-2; i>=0; i--) { node[i] = qry_f(node[2*i+1], node[2*i+2]); } } void lazy_eval(int k, int l, int r) { if(!need_upd[k]) return; node[k] = upd_f(node[k], lazy[k], l, r); if(r - l > 1) { lazy[2*k+1] = upd_f(lazy[2*k+1], lazy[k], 0, 1); lazy[2*k+2] = upd_f(lazy[2*k+2], lazy[k], 0, 1); need_upd[2*k+1] = need_upd[2*k+2] = true; } lazy[k] = I; need_upd[k] = false; } // 半開区間 [a, b) に対して値 val を反映させる // (upd_f を用いて処理) void update(int a, int b, T val, int l=0, int r=-1, int k=0) { if(r < 0) r = SIZE; lazy_eval(k, l, r); if(b <= l || r <= a) return; if(a <= l && r <= b) { lazy[k] = upd_f(lazy[k], val, 0, 1); need_upd[k] = true; lazy_eval(k, l, r); } else { int mid = (l + r) / 2; update(a, b, val, l, mid, 2*k+1); update(a, b, val, mid, r, 2*k+2); node[k] = qry_f(node[2*k+1], node[2*k+2]); } } // 半開区間 [a, b) に対してクエリを投げる // (qry_f を用いて処理) T query(int a, int b, int l=0, int r=-1, int k=0) { if(r < 0) r = SIZE; lazy_eval(k, l, r); if(b <= l || r <= a) return I; if(a <= l && r <= b) return node[k]; int mid = (l + r) / 2; T vl = query(a, b, l, mid, 2*k+1); T vr = query(a, b, mid, r, 2*k+2); return qry_f(vl, vr); } }; // HL 分解 // ある頂点 v の子たち c_i について、それを根とする部分木の頂点数が // 最大のものの 1 つを c_h とおく。このとき、辺 (v, c_h) は "heavy" な辺であるという。 // それ以外の辺は "light" な辺であるという。 // すべての辺を "heavy" と "light" に分類すると、木は "heavy" からなるパス // (下のコードだと chain) に分解 ("heavy" でつながっている頂点をひとつにまとめる) できる。 // これを HL 分解という。 // chain を縮約したあとの木の高さは O(log N) である。 // これは、"heavy" な辺の定義より、頂点 v と "light" な辺で結ばれている v の子それぞれに対して、 // 部分木の大きさが subsize(v) / 2 以下となっていることから導ける。 // "light" な辺を通るたびに部分木のサイズが半分以下になるため、深さは O(log N) となる。 // 移動元と行先と辺のコストを記録する構造体 template <typename T> struct Edge { int from, to; T cost; Edge(int s, T d) : to(s), cost(d) {} Edge(int f, int s, T d) : from(f), to(s), cost(d) {} bool operator<(const Edge &e) const { return cost < e.cost; } bool operator>(const Edge &e) const { return cost > e.cost; } }; template <typename T> using Graph = vector< vector< Edge<T> > >; template <typename Type> struct HLD { int N; const Graph<int> G; // ・元の木について // 根からの深さ、自分の親、自分を根にしたときの部分木のサイズ // および、その頂点から出る "heavy" な辺 vector<int> depth, parent, subsize, heavy; // ・chain について // chain の先頭要素、末尾要素、その頂点の 1 つ次・前の要素、chain 内でのインデックス vector<int> head, last, prev, next, chain, idx; // chain の情報 (同じ vector 内にある要素どうしは同じ chain に属する) vector< vector<int> > chains; // 各 chain に載せる遅延評価セグ木 (抽象) vector< lazysegtree<Type> > segs; HLD(const Graph<int> &H, int r=-1) : N(H.size()), G(H), depth(N, -1), parent(N, 0), subsize(N, 0), heavy(N, -1), head(N), last(N), prev(N, -1), next(N, -1), chain(N, -1), idx(N, 0) { if(r != -1) decompose(r); } // root を根として分解 void decompose(const int root) { stack<int> st; st.push(root); parent[root] = -1; depth[root] = 0; while(st.size()) { int cur_v = st.top(); st.pop(); // その頂点を初めて訪れた if(cur_v >= 0) { st.push(~cur_v); for(auto e : G[cur_v]) { // 親の方向でなければ値を更新 if(depth[e.to] != -1) continue; depth[e.to] = depth[cur_v] + 1; parent[e.to] = cur_v; st.push(e.to); } } // 帰りがけ else { int ma = 0; cur_v = ~cur_v; subsize[cur_v] = 1; for(auto e : G[cur_v]) { if(parent[cur_v] == e.to) continue; subsize[cur_v] += subsize[e.to]; if(ma < subsize[e.to]) { // cur_v と部分木のサイズが最大の子を結ぶ // (これが "heavy" の辺) ma = subsize[e.to]; heavy[cur_v] = e.to; } } } } st.push(root); while(st.size()) { int cur_v = st.top(); st.pop(); for(auto e : G[cur_v]) { if(parent[cur_v] != e.to) st.push(e.to); } // すでにその頂点について構築済みなら、何もしない // (chain の先頭の頂点でない) if(chain[cur_v] != -1) continue; chains.push_back(vector<int>()); vector<int> &path = chains.back(); // cur_v を始点として、heavy な辺をたどりながら下がる for(int v=cur_v; v!=-1; v=heavy[v]) { path.push_back(v); } for(size_t i=0; i<path.size(); i++) { // path (chain) の i 番目の頂点 v に関する情報 int v = path[i]; head[v] = path.front(), last[v] = path.back(); prev[v] = (i != 0 ? path[i-1] : -1); next[v] = (i+1 != path.size() ? path[i+1] : -1); chain[v] = (int)chains.size() - 1; idx[v] = i; } } } // chain の個数分だけ segtree を作る void buildSegtree(Type (*op1)(Type, Type, int, int), Type (*op2)(Type, Type), Type X) { segs.clear(); for(size_t i=0; i<chains.size(); i++) { segs.push_back(lazysegtree<Type>(chains[i].size(), op1, op2, X)); } } // 頂点 v について、どの chain に属するか、 // またその chain の中でのインデックスは何かを pair で返す pair<int, int> get_index(int v) { return make_pair(chain[v], idx[v]); } void debug_path() { for(size_t i=0; i<chains.size(); i++) { fprintf(stderr, "path:"); for(size_t k=0; k<chains[i].size(); k++) { fprintf(stderr, " %lld", chains[i][k]); } fprintf(stderr, "\n"); } } // v が属する chain の先頭を返す (head[v] が根なら -1 を返す) int climb(int v) { return parent[ head[v] ]; } // 頂点 u と v の最小共通祖先 int lca(int u, int v) { // 同じ chain に属するまで登る while(chain[u] != chain[v]) { if(depth[head[u]] < depth[head[v]]) v = climb(v); else u = climb(u); } // 属する chain が同じになれば、浅いほうが LCA return depth[u] < depth[v] ? u : v; } // lca で分解されたあとのパスについて、クエリを処理 void update_base(int u, int v, Type x, int f) { while(1) { // u の方が深い if(depth[u] < depth[v]) swap(u, v); // 属する chain が異なる → 先頭まで全部更新 if(chain[u] != chain[v]) { int lhs = 0, rhs = idx[u] + 1; segs[ chain[u] ].update(lhs, rhs, x); u = climb(u); } // 同じ → v を含めるかどうかに気をつけながら更新 else { int lhs = idx[v] + 1 - f, rhs = idx[u] + 1; segs[ chain[u] ].update(lhs, rhs, x); break; } } } // 頂点 u - v 間の「点」それぞれについて更新 // f は anc を更新対象に含めるかどうか void upd_vertex(int u, int v, Type x) { int anc = lca(u, v); update_base(u, anc, x, 1); update_base(v, anc, x, 0); } // 頂点 u - v 間の「辺」それぞれについて更新 void upd_edge(int u, int v, Type x) { int anc = lca(u, v); update_base(u, anc, x, 0); update_base(v, anc, x, 0); } // lca で分解されたあとのパスについて、クエリを処理 Type query_base(int u, int v, Type X, Type (*op)(Type, Type), int f) { Type ret = X; while(1) { if(depth[u] < depth[v]) swap(u, v); // A (根に近い) op B (遠い) の順番 (B op A ではない) // u と v の chain が異なるならば、chain の端まで上って次へ if(chain[u] != chain[v]) { int lhs = 0, rhs = idx[u] + 1; ret = op(segs[ chain[u] ].query(lhs, rhs), ret); u = climb(u); } // lhs を含むか含まないか (f = 1 で含む) else { int lhs = idx[v] + 1 - f, rhs = idx[u] + 1; ret = op(segs[ chain[u] ].query(lhs, rhs), ret); break; } } return ret; } // 頂点 u - v 間の「点」に関するクエリ (単位元と関数も必要) Type qry_vertex(int u, int v, Type X, Type (*op)(Type, Type)) { int anc = lca(u, v); Type ret = X; ret = op(ret, query_base(anc, u, X, op, 1)); ret = op(ret, query_base(anc, v, X, op, 0)); return ret; } // 頂点 u - v 間の「辺」に関するクエリ Type qry_edge(int u, int v, Type X, Type (*op)(Type, Type)) { int anc = lca(u, v); Type ret = X; ret = op(ret, query_base(anc, u, X, op, 0)); ret = op(ret, query_base(anc, v, X, op, 0)); return ret; } }; // 関節点を求める (artPoints) // 橋を求める (bridges) // 二重辺連結成分分解をする (BICC) // 関節点は、取り除いたときに連結でなくなってしまうような頂点のこと // 橋は、取り除いた時に連結でなくなってしまうような辺のこと template <typename T> struct graphLink { vector<int> ord, low, parent, cmp; vector< vector< Edge<T> > > G, H; // 橋の情報 (first < second となるように格納) vector< pair<int, int> > bridges; int cnt, v; // init graphLink(vector< vector< Edge<T> > > &S, int root=0) { int n = S.size(); ord.resize(n, -1), low.resize(n, 0), parent.resize(n, -1), cmp.resize(n, -1); cnt = 0, v = n; G = S; dfs(root); } // 橋であるかの判定 bool is_bridge(int x, int y) { if(ord[x] > ord[y]) swap(x, y); return ord[x] < low[y]; } // dfs 木の作成と橋の列挙 (初期化と同時にやる) // usage: dfs(root); void dfs(int cur, int prev=-1) { low[cur] = cnt; ord[cur] = cnt++; for(auto x : G[cur]) { if(x.to == prev) continue; if(ord[x.to] < 0) { parent[x.to] = cur; dfs(x.to, cur); low[cur] = min(low[cur], low[x.to]); } else { low[cur] = min(low[cur], ord[x.to]); } if(is_bridge(cur, x.to)) { int a = min(cur, x.to); int b = max(cur, x.to); bridges.emplace_back(make_pair(a, b)); } } } // 関節点を求める (root は dfs 木の root と一致させる) // root は子を 2 つ持っていれば関節点になる // それ以外の頂点に関しては ord[parent] <= low[i] のとき関節点になる // (lowlink でも親より深さが低い頂点にたどり着けないため) set<int> artPoints(int root) { set<int> se; int num = 0; for(int i=0; i<v; i++) { if(parent[i] < 0) continue; if(parent[i] == root) num++; else if(ord[parent[i]] <= low[i]) se.insert(parent[i]); } if(num >= 2) se.insert(0); return se; } // 二重辺連結成分分解 (橋となる辺を使わないように DFS) // Verified: AtCoder Regular Contest D: 旅行会社高橋君 void BICC() { int k = 0; // point, number stack<pii> S; for(int i=0; i<v; i++) { if(cmp[i] >= 0) continue; cmp[i] = k; S.push(make_pair(i, k++)); while(!S.empty()) { pii cur = S.top(); S.pop(); for(auto x : G[cur.first]) { if(cmp[x.to] >= 0) continue; if(is_bridge(cur.first, x.to)) continue; cmp[x.to] = cur.second; S.push(make_pair(x.to, cmp[x.to])); } } } set<pii> edge_set; H.resize(k); for(int i=0; i<v; i++) { for(auto x : G[i]) { int ca = cmp[i], cb = cmp[x.to]; if(ca == cb) continue; if(ca > cb) swap(ca, cb); if(edge_set.count(make_pair(ca, cb))) continue; edge_set.insert(make_pair(ca, cb)); H[ca].push_back(Edge<T>(cb, 1)); H[cb].push_back(Edge<T>(ca, 1)); } } } }; // [[つかいかた]] // update 用と query 用の関数と単位元 (関数を噛ませても結果が変わらないもの) を用意して宣言する // 下の例だと、update 用の関数が upd で、query 用の関数が fnd で、単位元が 0 // 範囲が [l, r) のノードの値 a に値 b を反映させる ll upd(ll a, ll b, int l, int r) {return b;} // ノードの値 a と b に対してどのような操作をするか? ll fnd(ll a, ll b) {return max(a, b);} signed main() { int N, M, Q; scanf("%lld%lld%lld", &N, &M, &Q); Graph<int> G(N); for(int i=0; i<M; i++) { int u, v; scanf("%lld%lld", &u, &v); u--; v--; G[u].push_back(Edge<int>(v, 1)); G[v].push_back(Edge<int>(u, 1)); } graphLink<int> tree(G, 0); tree.BICC(); HLD<int> hl(tree.H, 0); hl.buildSegtree(upd, fnd, -1); map<int, int> weight_to_idx; int tree_size = tree.H.size(); vector< priority_queue<int> > que(tree_size); for(int i=0; i<Q; i++) { int query; scanf("%lld", &query); if(query == 1) { int U, W; scanf("%lld%lld", &U, &W); U--; int idx = tree.cmp[U]; if(que[idx].empty() || que[idx].top() < W) { hl.upd_vertex(idx, idx, W); } que[idx].push(W); weight_to_idx[W] = idx; } if(query == 2) { int S, T; scanf("%lld%lld", &S, &T); S--; T--; int s = tree.cmp[S], t = tree.cmp[T]; int ans = hl.qry_vertex(s, t, -1, fnd); printf("%lld\n", ans); if(ans > 0) { int idx = weight_to_idx[ans]; que[idx].pop(); int nval = (que[idx].size() ? que[idx].top() : -1); hl.upd_vertex(idx, idx, nval); } } } return 0; }