White Falcon And Tree
Difficulty: Hard

White Falcon has a tree with $N$ nodes. Each node contains a linear function. Let's denote by $f_u(x)$ the linear function contained in the node $u$.  

Let's denote the path from node $u$ to node $v$ like this: $p_1,p_2,p_3,\ldots,p_k$, where $p_1 = u$ and $p_k = v$, and $p_i$ and $p_{i+1}$ are connected.  

White Falcon also has $Q$ queries. They are in the following format:  

1. $1$ $u$ $v$ $a$ $b$. Assign $ax + b$ as the function of all the nodes on the path from $u$ to $v$, i.e., $f_{p_i}(x)$ is changed to $ax+b$ where $p_1,p_2,p_3,\ldots,p_k$ is the path from $u$ to $v$.

2. $2$ $u$ $v$ $x$. Calculate $f_{p_k}(f_{p_{k-1}}(f_{p_{k-2}}(\ldots f_{p_1}(x))))$ modulo $(10^9 + 7)$


Input Format:
The first line contains $N$, the number of nodes. The following $N$ lines each contain two integers $a$ and $b$ that describe the function $ax + b$.  

Following $N - 1$ lines contain edges of the tree. 

The next line contains $Q$, the number of queries. Each subsequent line contains one of the queries described above.

Output Format:
For every query of the second kind, print one line containing an integer, the answer for that query.  

**Constraints**  
$1 \leq  N \leq  50000$ (Number of nodes)   
$1 \leq  Q \leq  50000$ (Number of queries)   
$0 \leq a, b, x < 10^9 + 7$


Sample Input:
    2
    1 1
    1 2
    1 2
    2
    1 2 2 1 1
    2 1 2 1

Sample Output:
3

Python 3 starter code:


Reference solution (C++):
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    #include <string.h>
    #include <time.h>
    #define fo(i,a,b) dfo(int,i,a,b)
    #define fr(i,n) dfr(int,i,n)
    #define fe(i,a,b) dfe(int,i,a,b)
    #define fq(i,n) dfq(int,i,n)
    #define nfo(i,a,b) dfo(,i,a,b)
    #define nfr(i,n) dfr(,i,n)
    #define nfe(i,a,b) dfe(,i,a,b)
    #define nfq(i,n) dfq(,i,n)
    #define dfo(d,i,a,b) for (d i = (a); i < (b); i++)
    #define dfr(d,i,n) dfo(d,i,0,n)
    #define dfe(d,i,a,b) for (d i = (a); i <= (b); i++)
    #define dfq(d,i,n) dfe(d,i,1,n)
    #define ffo(i,a,b) dffo(int,i,a,b)
    #define ffr(i,n) dffr(int,i,n)
    #define ffe(i,a,b) dffe(int,i,a,b)
    #define ffq(i,n) dffq(int,i,n)
    #define nffo(i,a,b) dffo(,i,a,b)
    #define nffr(i,n) dffr(,i,n)
    #define nffe(i,a,b) dffe(,i,a,b)
    #define nffq(i,n) dffq(,i,n)
    #define dffo(d,i,a,b) for (d i = (b)-1; i >= (a); i--)
    #define dffr(d,i,n) dffo(d,i,0,n)
    #define dffe(d,i,a,b) for (d i = (b); i >= (a); i--)
    #define dffq(d,i,n) dffe(d,i,1,n)
    #define ll long long
    #define alok(n,t) ((t*)malloc((n)*sizeof(t)))
    #define pf printf
    #define sf scanf
    #define pln pf("\n")
    #define flsh fflush(stdout)
    #include <map>
    #include <algorithm>
    #include <vector>
    #include <set>
    #include <iostream>
    using namespace std;

    #define mod 1000000007

    // linear operator x -> ax+b
    struct op {
        int a, b;
        op(int a = 1, int b = 0) : a(a), b(b) {}

        // compose this and v
        op operator*(op v) {
            return op(a * (ll)v.a % mod, (a * (ll)v.b + b) % mod);
        }

        // evaluate this operator at x
        int operator() (int x) {
            return (a * (ll)x + b) % mod;
        }
    };

    // identity operator
    op one = op();

    // tree node
    struct node {
        vector<int> adj; // neighbors
        op o;
        int parent;
        int size;
        int hson;
        int hindex; // index of heavy path
        int hpos;   // position in heavy path

        node(): parent(-1), size(0), hson(-1), hindex(-1) {}
    };

    vector<int> pre;
    vector<node> nodes(111111);
    vector<op> ops(111111);

    // segment tree node
    struct snode {
        int i, j, k;
        op val;
        op *setv;
        snode *lft;
        snode *ryt;

        // make a segment tree in [i,j)
        snode (int i, int j, int k): i(i), j(j), k(k), setv(NULL) {
            if (j - i == 1) {
                val = ops[i];
                lft = ryt = NULL;
            } else {
                int l = i + j >> 1;
                lft = new snode(i, l, k-1);
                ryt = new snode(l, j, k-1);
                val = lft->val * ryt->val;
            }
        }

        // visit a node (propagate downwards if necessary)
        #define visit() do {\
            if (setv) {\
                if (lft) {\
                    lft->setv = setv;\
                    ryt->setv = setv;\
                }\
                val = setv[k];\
                setv = NULL;\
            }\
        } while (0)

        // get product in [I,J) in this node
        op get(int I, int J) {
            visit();
            if (I <= i and j <= J) {
                return val;
            } else if (J <= i or j <= I) {
                return one;
            } else {
                return lft->get(I, J) * ryt->get(I, J);
            }
        }

        // set values in [I,J) in this node to v 
        void set(int I, int J, op *v) {
            if (I <= i and j <= J) {
                setv = v;
                visit();
            } else {
                visit();
                if (!(J <= i or j <= I)) {
                    lft->set(I, J, v);
                    ryt->set(I, J, v);
                    val = lft->val * ryt->val;
                }
            }
        }
    };

    struct path {
        vector<int> inds;
        int size;
        int parent; // parent node of top of heavy path
        snode *uptree;
        snode *dntree;
        void initialize() {
            size = inds.size();
            int k = 0;
            while (1 << k < size) k++;
            fr(i,size) ops[i] = nodes[inds[i]].o;
            dntree = new snode(0, 1<<k, k);
            fr(i,size) ops[i] = nodes[inds[size-1-i]].o;
            uptree = new snode(0, 1<<k, k);
        }
        void set(int i, int j, op *v) {
            // smart enough to know up and down
            if (i <= j) {
                dntree->set(i, j+1, v);
                uptree->set(size-1-j, size-i, v);
            } else {
                dntree->set(j, i+1, v);
                uptree->set(size-1-i, size-j, v);
            }
        }
        op get(int i, int j) {
            // smart enough to know up and down
            if (i <= j) {
                return dntree->get(i, j+1);
            } else {
                return uptree->get(size-1-i, size-j);
            }
        }
    };

    vector<path> paths(111111);

    int main() {
        int n;
        sf("%d", &n);

        // collect ops
        fr(i,n) {
            int a, b;
            sf("%d%d", &a, &b);
            nodes[i].o = op(a, b);
        }

        // collect edges
        fr(i,n-1) {
            int a, b;
            sf("%d%d", &a, &b);
            a--, b--;
            nodes[a].adj.push_back(b);
            nodes[b].adj.push_back(a);
        }

        // calculate preorder and parents
        #define add(i,p) (nodes[i].parent = p, pre.push_back(i))
        add(0,-1);
        fr(f,n) {
            int i = pre[f];
            fr(nb,nodes[i].adj.size()) {
                int j = nodes[i].adj[nb];
                if (nodes[i].parent == j) continue;
                add(j,i);
            }
        }
        #undef add

        // calculate subtree sizes
        ffo(f,1,n) {
            int i = pre[f];
            int p = nodes[i].parent;
            nodes[p].size += ++nodes[i].size;
            nodes[p].hson = i;
        }
        nodes[0].size++;

        // calculate heavy sons
        ffo(f,1,n) {
            int i = pre[f];
            int p = nodes[i].parent;
            if (nodes[nodes[p].hson].size < nodes[i].size) {
                nodes[p].hson = i;
            }
        }

        // calculate heavy index
        int hcount = 0;
        fr(f,n) {
            int i = pre[f];
            int idx = nodes[i].hindex;
            if (!~idx) {
                idx = nodes[i].hindex = hcount++;
                paths[idx].parent = nodes[i].parent;
            }
            nodes[i].hpos = paths[idx].inds.size();
            paths[idx].inds.push_back(i);
            int s = nodes[i].hson;
            if (~s) nodes[s].hindex = idx;
        }

        // initialize segment trees
        fr(h,hcount) paths[h].initialize();

        // calculate power of two for n
        int k = 0;
        while (1 << k < n) k++;
        k++;

        // process queries
        int q;
        sf("%d", &q);
        while (q--) {
            int t;
            sf("%d", &t);
            if (t == 1) {
                // update query
                int u, v, a, b;
                sf("%d%d%d%d", &u, &v, &a, &b);
                u--, v--;

                // allocate powers of operator
                op *o = alok(k, op);
                o[0] = op(a, b);
                fo(i,1,k) o[i] = o[i-1] * o[i-1];

                // ascend and set in paths
                #define ascend(i) do {\
                    int h = nodes[i].hindex;\
                    paths[h].set(0, nodes[i].hpos, o);\
                    i = paths[h].parent;\
                } while (0)
                while (nodes[u].hindex != nodes[v].hindex) {
                    if (nodes[u].hindex > nodes[v].hindex) {
                        ascend(u);
                    } else {
                        ascend(v);
                    }
                }
                #undef ascend

                // set in topmost path
                int h = nodes[u].hindex;
                paths[h].set(nodes[u].hpos, nodes[v].hpos, o);
            } else {
                // get query
                int u, v, x;
                sf("%d%d%d", &u, &v, &x);
                u--, v--;

                // ascend and calculate operator in paths
                op l = one;
                while (nodes[u].hindex != nodes[v].hindex) {
                    if (nodes[u].hindex > nodes[v].hindex) {
                        int h = nodes[u].hindex;
                        x = paths[h].get(0, nodes[u].hpos)(x);
                        u = paths[h].parent;
                    } else {
                        int h = nodes[v].hindex;
                        l = l * paths[h].get(nodes[v].hpos, 0);
                        v = paths[h].parent;
                    }
                }

                // operator in topmost path
                int h = nodes[u].hindex;
                x = paths[h].get(nodes[v].hpos, nodes[u].hpos)(x);

                // calculate and print the answer
                int ans = l(x);
                if (ans < 0) ans += mod;
                pf("%d\n", ans);
            }
        }
    }