#include using namespace std; using ll = long long; constexpr char newl = '\n'; // https://noshi91.hatenablog.com/entry/2019/03/31/174006 template struct ModInt { using u64 = std::uint_fast64_t; static constexpr u64 MOD = Modulus; u64 val; constexpr ModInt(const u64 x = 0) noexcept : val(x % MOD) {} constexpr ModInt operator+() const noexcept { return ModInt(*this); } constexpr ModInt operator-() const noexcept { ModInt res(*this); if (res.val != 0) res.val = MOD - res.val; return res; } constexpr bool operator==(const ModInt& rhs) const noexcept { return val == rhs.val; } constexpr bool operator!=(const ModInt& rhs) const noexcept { return val != rhs.val; } // prefix increment/decrement constexpr ModInt& operator++() noexcept { return *this += ModInt(1); } constexpr ModInt& operator--() noexcept { return *this -= ModInt(1); } // postfix increment/decrement constexpr ModInt& operator++(int) noexcept { ModInt tmp(*this); ++*this; return tmp; } constexpr ModInt& operator--(int) noexcept { ModInt tmp(*this); --*this; return tmp; } constexpr ModInt operator+(const ModInt& rhs) const noexcept { return ModInt(*this) += rhs; } constexpr ModInt operator-(const ModInt& rhs) const noexcept { return ModInt(*this) -= rhs; } constexpr ModInt operator*(const ModInt& rhs) const noexcept { return ModInt(*this) *= rhs; } constexpr ModInt operator/(const ModInt& rhs) const noexcept { return ModInt(*this) /= rhs; } constexpr ModInt& operator+=(const ModInt& rhs) noexcept { val += rhs.val; if (val >= MOD) val -= MOD; return *this; } constexpr ModInt& operator-=(const ModInt& rhs) noexcept { if (val < rhs.val) val += MOD; val -= rhs.val; return *this; } constexpr ModInt& operator*=(const ModInt& rhs) noexcept { val = val * rhs.val % MOD; return *this; } // prime Modulus only constexpr ModInt& operator/=(const ModInt& rhs) noexcept { return *this *= rhs.inv(); } // prime Modulus only constexpr ModInt inv() const noexcept { return pow(*this, MOD - 2); } }; template constexpr ModInt pow(ModInt x, std::uint_fast64_t n) { ModInt res(1); while (n) { if (n & 1) res *= x; x *= x; n >>= 1; } return res; } template istream& operator>>(istream& is, ModInt& x) { std::uint_fast64_t val; is >> val; x = ModInt(val); return is; } template ostream& operator<<(ostream& os, const ModInt& x) { return os << x.val; } using mint = ModInt<1000000007>; // https://qiita.com/ageprocpp/items/8dfe768218da83314989 // http://codeforces.com/blog/entry/53170 // https://github.com/ningenMe/compro-library/blob/master/lib/graph/Tree.cpp#L220 // https://beet-aizu.github.io/library/library/tree/heavylightdecomposition.cpp.html // https://ei1333.github.io/luzhiled/snippets/tree/heavy-light-decomposition.html class HLD { public: using Graph = vector< vector >; using Segment = pair; private: void dfs_size(int cur, int par) { // if g[cur][0] == par, always sub_size[nex] < sub_size[par] // and this will be broken... if (!g[cur].empty() && g[cur][0] == par) swap(g[cur][0], g[cur].back()); for (int& nex : g[cur]) { if (nex == par) continue; parent[nex] = cur; dfs_size(nex, cur); sub_size[cur] += sub_size[nex]; if (sub_size[nex] > sub_size[g[cur][0]]) { swap(nex, g[cur][0]); } } } // head: HLD // in, out: Euler Tour void dfs_hld(int cur, int par, int& times) { in[cur] = times++; for (int nex : g[cur]) { if (nex == par) continue; // if nex == g[cur][0]: heavy edge // else: light edge head[nex] = (nex == g[cur][0] ? head[cur] : nex); dfs_hld(nex, cur, times); } out[cur] = times; } // convert node/edge path to segments // segment: [l, r) // is_edge_path ? edge path : node path vector to_segments(int u, int v, bool is_edge_path) { vector segments; while (true) { if (in[u] > in[v]) swap(u, v); if (head[u] == head[v]) { if (u != v || !is_edge_path) { segments.emplace_back(in[u] + is_edge_path, in[v] + 1); } break; } segments.emplace_back(in[head[v]], in[v] + 1); v = parent[head[v]]; } return segments; } public: Graph g; vector sub_size, parent, in, out, head; HLD(const Graph& tree, const int root = 0) : g(tree), sub_size(tree.size(), 1), parent(tree.size(), -1), in(tree.size()), out(tree.size()), head(tree.size(), root) { dfs_size(root, -1); int times = 0; dfs_hld(root, -1, times); } int lca(int u, int v) { while (true) { if (in[u] > in[v]) swap(u, v); if (head[u] == head[v]) return u; v = parent[head[v]]; } } inline vector node_path_to_segments(int u, int v) { return to_segments(u, v, false); } // you have to convert edge cost to node cost // see: https://www.hamayanhamayan.com/entry/2017/04/10/172636 inline vector edge_path_to_segments(int u, int v) { return to_segments(u, v, true); } Segment subtree_to_segment(int v) { return {in[v], out[v]}; } }; using vec = vector; using mat = vector; mat mul(const mat& A, const mat& B) { mat C(A.size(), vec(B[0].size(), 0)); for (int i = 0; i < A.size(); i++) { for (int k = 0; k < B.size(); k++) { for (int j = 0; j < B[0].size(); j++) { C[i][j] += A[i][k] * B[k][j]; } } } return C; } mat pow(mat A, ll n) { mat B(A.size(), vec(A.size(), 0)); for (int i = 0; i < A.size(); i++) { B[i][i] = 1; } while (n > 0) { if (n & 1) B = mul(B, A); A = mul(A, A); n >>= 1; } return B; } template struct SegmentTree { using T = typename Monoid::T; int n; vector data; SegmentTree() {} SegmentTree(int size, T initial_value = Monoid::unit()) { n = 1; while (n < size) n <<= 1; data.assign(2 * n - 1, initial_value); if (initial_value != Monoid::unit()) { for (int i = n - 2; i >= 0; i--) data[i] = Monoid::merge(data[i * 2 + 1], data[i * 2 + 2]); } } SegmentTree(const vector& v) { int size = v.size(); n = 1; while (n < size) n <<= 1; data.assign(2 * n - 1, Monoid::unit()); for (int i = 0; i < size; i++) data[i + n - 1] = v[i]; for (int i = n - 2; i >= 0; i--) data[i] = Monoid::merge(data[i * 2 + 1], data[i * 2 + 2]); } T getLeaf(int k) { return data[k + n - 1]; } void update(int k, T x) { k += n - 1; //葉の節点 Monoid::update(data[k], x); while (k > 0) { k = (k - 1) / 2; data[k] = Monoid::merge(data[k * 2 + 1], data[k * 2 + 2]); } } //区間[a, b)に対するクエリに答える //k:節点番号, [l, r):節点に対応する区間 T query(int a, int b, int k, int l, int r) { //[a, b)と[l, r)が交差しない場合 if (r <= a || b <= l) return Monoid::unit(); //[a, b)が[l, r)を含む場合、節点の値 if (a <= l && r <= b) return data[k]; else { //二つの子をマージ T vl = query(a, b, k * 2 + 1, l, (l + r) / 2); T vr = query(a, b, k * 2 + 2, (l + r) / 2, r); return Monoid::merge(vl, vr); } } //外から呼ぶ用 T query(int a, int b) { return query(a, b, 0, 0, n); } //非再帰版: バグってるかもしれないので定数倍高速化する時以外使わないで //区間[a, b)に対するクエリに答える T query_fast(int a, int b) { T vl = Monoid::unit(), vr = Monoid::unit(); for (int l = a + n, r = b + n; l != r; l >>= 1, r >>= 1) { if (l & 1) vl = Monoid::merge(vl, data[l++ - 1]); if (r & 1) vr = Monoid::merge(data[--r - 1], vr); } return Monoid::merge(vl, vr); } }; template struct RangeMul { using T = U; static T merge(T x, T y) { return mul(x, y); } static void update(T& target, T x) { target = x; } static constexpr T unit() { return T({{1, 0}, {0, 1}}); } }; int main() { cin.tie(nullptr); ios::sync_with_stdio(false); int n; cin >> n; HLD::Graph g(n); vector a(n - 1), b(n - 1); for (int i = 0; i < n - 1; i++) { cin >> a[i] >> b[i]; g[a[i]].push_back(b[i]); g[b[i]].push_back(a[i]); } HLD hld(g); SegmentTree< RangeMul<> > st(n); int q; cin >> q; for (int i = 0; i < q; i++) { char command; cin >> command; if (command == 'x') { int j; cin >> j; mat x(2, vec(2)); for (int k = 0; k < 2; k++) { for (int l = 0; l < 2; l++) { cin >> x[k][l]; } } for (auto& seg : hld.edge_path_to_segments(a[j], b[j])) { if (seg.first == seg.second) continue; st.update(seg.first, x); } } else { int j, k; cin >> j >> k; mat ans = RangeMul<>::unit(); for (auto& seg : hld.edge_path_to_segments(j, k)) { ans = mul(st.query_fast(seg.first, seg.second), ans); } for (int l = 0; l < 2; l++) { for (int m = 0; m < 2; m++) { cout << ans[l][m] << " \n"[l == 1 && m == 1]; } } } } return 0; }