import java.util.*;

// LinkCut tree with path queries. Query complexity is O(log(n)) amortized.
// Based on Daniel Sleator's implementation http://www.codeforces.com/contest/117/submission/860934
public class LinkCutTree {

	// Modify the following 5 methods to implement your custom operations on the tree.
	// This example implements Add/Sum operations. Operations like Add/Max, Set/Max can also be implemented.
	static int modifyOperation(int x, int y) {
		return x + y;
	}

	// query (or combine) operation
	static int queryOperation(int leftValue, int rightValue) {
		return leftValue + rightValue;
	}

	static int deltaEffectOnSegment(int delta, int segmentLength) {
		if (delta == getNeutralDelta()) return getNeutralDelta();
		// Here you must write a fast equivalent of following slow code:
		// int result = delta;
		// for (int i = 1; i < segmentLength; i++) result = queryOperation(result, delta);
		// return result;
		return delta * segmentLength;
	}

	static int getNeutralDelta() {
		return 0;
	}

	static int getNeutralValue() {
		return 0;
	}

	// generic code
	static int joinValueWithDelta(int value, int delta) {
		if (delta == getNeutralDelta()) return value;
		return modifyOperation(value, delta);
	}

	static int joinDeltas(int delta1, int delta2) {
		if (delta1 == getNeutralDelta()) return delta2;
		if (delta2 == getNeutralDelta()) return delta1;
		return modifyOperation(delta1, delta2);
	}

	public static class Node {
		int nodeValue;
		int subTreeValue;
		int delta; // delta affects nodeValue, subTreeValue, left.delta and right.delta
		int size;
		boolean revert;
		Node left;
		Node right;
		Node parent;

		Node(int value) {
			nodeValue = value;
			subTreeValue = value;
			delta = getNeutralDelta();
			size = 1;
		}

		// tests whether x is a root of a splay tree
		boolean isRoot() {
			return parent == null || (parent.left != this && parent.right != this);
		}

		void push() {
			if (revert) {
				revert = false;
				Node t = left;
				left = right;
				right = t;
				if (left != null)
					left.revert = !left.revert;
				if (right != null)
					right.revert = !right.revert;
			}

			nodeValue = joinValueWithDelta(nodeValue, delta);
			subTreeValue = joinValueWithDelta(subTreeValue, deltaEffectOnSegment(delta, size));
			if (left != null)
				left.delta = joinDeltas(left.delta, delta);
			if (right != null)
				right.delta = joinDeltas(right.delta, delta);
			delta = getNeutralDelta();
		}

		void update() {
			subTreeValue = queryOperation(queryOperation(getSubTreeValue(left), joinValueWithDelta(nodeValue, delta)), getSubTreeValue(right));
			size = 1 + getSize(left) + getSize(right);
		}
	}

	static int getSize(Node root) {
		return root == null ? 0 : root.size;
	}

	static int getSubTreeValue(Node root) {
		return root == null ? getNeutralValue() : joinValueWithDelta(root.subTreeValue, deltaEffectOnSegment(root.delta, root.size));
	}

	static void connect(Node ch, Node p, Boolean isLeftChild) {
		if (ch != null)
			ch.parent = p;
		if (isLeftChild != null) {
			if (isLeftChild)
				p.left = ch;
			else
				p.right = ch;
		}
	}

	// rotates edge (x, x.parent)
	//        g            g
	//       /            /
	//      p            x
	//     / \    ->    / \
	//    x  p.r      x.l  p
	//   / \              / \
	// x.l x.r          x.r p.r
	static void rotate(Node x) {
		Node p = x.parent;
		Node g = p.parent;
		boolean isRootP = p.isRoot();
		boolean leftChildX = (x == p.left);

		// create 3 edges: (x.r(l),p), (p,x), (x,g)
		connect(leftChildX ? x.right : x.left, p, leftChildX);
		connect(p, x, !leftChildX);
		connect(x, g, isRootP ? null : p == g.left);
		p.update();
	}

	// brings x to the root, balancing tree
	//
	// zig-zig case
	//        g                                  x
	//       / \               p                / \
	//      p  g.r rot(p)    /   \     rot(x) x.l  p
	//     / \      -->    x       g    -->       / \
	//    x  p.r          / \     / \           x.r  g
	//   / \            x.l x.r p.r g.r             / \
	// x.l x.r                                    p.r g.r
	//
	// zig-zag case
	//      g               g
	//     / \             / \               x
	//    p  g.r rot(x)   x  g.r rot(x)    /   \
	//   / \      -->    / \      -->    p       g
	// p.l  x           p  x.r          / \     / \
	//     / \         / \            p.l x.l x.r g.r
	//   x.l x.r     p.l x.l
	static void splay(Node x) {
		while (!x.isRoot()) {
			Node p = x.parent;
			Node g = p.parent;
			if (!p.isRoot())
				g.push();
			p.push();
			x.push();
			if (!p.isRoot())
				rotate((x == p.left) == (p == g.left) ? p/*zig-zig*/ : x/*zig-zag*/);
			rotate(x);
		}
		x.push();
		x.update();
	}

	// makes node x the root of the virtual tree, and also x becomes the leftmost node in its splay tree
	static Node expose(Node x) {
		Node last = null;
		for (Node y = x; y != null; y = y.parent) {
			splay(y);
			y.left = last;
			last = y;
		}
		splay(x);
		return last;
	}

	public static void makeRoot(Node x) {
		expose(x);
		x.revert = !x.revert;
	}

	public static boolean connected(Node x, Node y) {
		if (x == y)
			return true;
		expose(x);
		// now x.parent is null
		expose(y);
		return x.parent != null;
	}

	public static void link(Node x, Node y) {
		if (connected(x, y))
			throw new RuntimeException("error: x and y are already connected");
		makeRoot(x);
		x.parent = y;
	}

	public static void cut(Node x, Node y) {
		makeRoot(x);
		expose(y);
		// check that exposed path consists of a single edge (y,x)
		if (y.right != x || x.left != null)
			throw new RuntimeException("error: no edge (x,y)");
		y.right.parent = null;
		y.right = null;
	}

	public static int query(Node from, Node to) {
		makeRoot(from);
		expose(to);
		return getSubTreeValue(to);
	}

	public static void modify(Node from, Node to, int delta) {
		makeRoot(from);
		expose(to);
		to.delta = joinDeltas(to.delta, delta);
	}

	// random test
	public static void main(String[] args) {
		Random rnd = new Random(1);
		for (int step = 0; step < 1_000; step++) {
			int n = rnd.nextInt(50) + 1;
			boolean[][] g = new boolean[n][n];
			int[] val = new int[n];
			Node[] nodes = new Node[n];
			for (int i = 0; i < n; i++)
				nodes[i] = new Node(0);
			for (int query = 0; query < 2_000; query++) {
				int cmd = rnd.nextInt(10);
				int u = rnd.nextInt(n);
				int v = rnd.nextInt(n);
				Node x = nodes[u];
				Node y = nodes[v];
				if (cmd == 0) {
					makeRoot(x);
					expose(y);
					if (y.right == x && x.left == null && x.right == null) {
						cut(x, y);
						g[u][v] = g[v][u] = false;
					}
				} else if (cmd == 1) {
					if (connected(x, y)) {
						List<Integer> path = new ArrayList<>();
						getPathFromAtoB(g, u, v, -1, path);
						int res = getNeutralValue();
						for (int i : path)
							res = queryOperation(res, val[i]);
						if (query(x, y) != res)
							throw new RuntimeException();
					}
				} else if (cmd == 2) {
					if (connected(x, y)) {
						List<Integer> path = new ArrayList<>();
						getPathFromAtoB(g, u, v, -1, path);
						int delta = rnd.nextInt(100) + 1;
						for (int i : path)
							val[i] = joinValueWithDelta(val[i], delta);
						modify(x, y, delta);
					}
				} else {
					if (!connected(x, y)) {
						link(x, y);
						g[u][v] = g[v][u] = true;
					}
				}
			}
		}
		System.out.println("Test passed");
	}

	static boolean getPathFromAtoB(boolean[][] tree, int u, int v, int p, List<Integer> path) {
		path.add(u);
		if (u == v)
			return true;
		for (int i = 0; i < tree.length; i++)
			if (i != p && tree[u][i] && getPathFromAtoB(tree, i, v, u, path))
				return true;
		path.remove(path.size() - 1);
		return false;
	}
}
