There is a sequence $A = a_0, a_1, ..., a_{n-1}$. You are given the following information and questions.

* relate$(x, y, z)$: $a_y$ is greater than $a_x$ by $z$
* diff$(x, y)$: report the difference between $a_x$ and $a_y$ $(a_y - a_x)$

Constraints

* $2 \leq n \leq 100,000$
* $1 \leq q \leq 200,000$
* $0 \leq x, y < n$
* $x \ne y$
* $0 \leq z \leq 10000$
* There are no inconsistency in the given information

Input


$n \; q$
$query_1$
$query_2$
:
$query_q$


In the first line, $n$ and $q$ are given. Then, $q$ information/questions are given in the following format.


0 $x \; y\; z$


or


1 $x \; y$


where '0' of the first digit denotes the relate information and '1' denotes the diff question.

Output

For each diff question, print the difference between $a_x$ and $a_y$ $(a_y - a_x)$.

Example

Input

5 6
0 0 2 5
0 1 2 3
1 0 1
1 1 3
0 1 4 8
1 0 4


Output

2
?
10

Difficulty: UNKNOWN_DIFFICULTY

Starter code:

Solutions:
line = input()
(n, q) = list(map(int, line.split()))
rel = {}
for i in range(0, n):
	rel[i] = []
parent = [i for i in range(0, n)]
rank = [0] * n
weight = [0] * n

def find(x):
	if parent[x] == x:
		return x
	else:
		y = find(parent[x])
		weight[x] += weight[parent[x]]
		parent[x] = y
		return y

def union(x, y, w):
	rx = find(x)
	ry = find(y)
	if rank[rx] < rank[ry]:
		parent[rx] = ry
		weight[rx] = w - weight[x] + weight[y]
	else:
		parent[ry] = rx
		weight[ry] = -w - weight[y] + weight[x]
		if rank[rx] == rank[ry]:
			rank[rx] += 1

def same(x, y):
	return find(x) == find(y)

def diff(x, y):
	return weight[x] - weight[y]
for _ in range(0, q):
	line = input()
	query = list(map(int, line.split()))
	if query[0] == 0:
		(x, y, z) = query[1:]
		union(x, y, z)
	elif query[0] == 1:
		(x, y) = query[1:]
		if same(x, y):
			print(diff(x, y))
		else:
			print('?')


class WeightedUnionFindTree:

	def __init__(self, n):
		self.par = [i for i in range(n + 1)]
		self.rank = [0] * (n + 1)
		self.weight = [0] * (n + 1)

	def find(self, x):
		if self.par[x] == x:
			return x
		else:
			y = self.find(self.par[x])
			self.weight[x] += self.weight[self.par[x]]
			self.par[x] = y
			return y

	def unite(self, x, y, w):
		rx = self.find(x)
		ry = self.find(y)
		if self.rank[rx] < self.rank[ry]:
			self.par[rx] = ry
			self.weight[rx] = w - self.weight[x] + self.weight[y]
		else:
			self.par[ry] = rx
			self.weight[ry] = -w - self.weight[y] + self.weight[x]
			if self.rank[rx] == self.rank[ry]:
				self.rank[rx] += 1

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def diff(self, x, y):
		return self.weight[x] - self.weight[y]
(n, q) = [int(x) for x in input().split(' ')]
wuft = WeightedUnionFindTree(n - 1)
for i in range(q):
	query = [int(x) for x in input().split(' ')]
	if query[0] == 0:
		(x, y, z) = query[1:]
		wuft.unite(x, y, z)
	elif query[0] == 1:
		(x, y) = query[1:]
		if wuft.same(x, y):
			print(wuft.diff(x, y))
		else:
			print('?')


class UnionSet:

	def __init__(self, nmax: int) -> None:
		self.size = [1] * nmax
		self.id = [i for i in range(nmax)]
		self.weight = [0] * nmax

	def root(self, i: int) -> int:
		if i == self.id[i]:
			return i
		else:
			tmp = self.root(self.id[i])
			self.weight[i] += self.weight[self.id[i]]
			self.id[i] = tmp
			return self.id[i]

	def get_weight(self, i: int) -> int:
		self.root(i)
		return self.weight[i]

	def connected(self, p: int, q: int) -> bool:
		return self.root(p) == self.root(q)

	def unite(self, p: int, q: int, w: int) -> None:
		w += self.get_weight(p)
		w -= self.get_weight(q)
		(i, j) = (self.root(p), self.root(q))
		if i == j:
			return
		if self.size[i] < self.size[j]:
			self.id[i] = j
			self.size[j] += self.size[i]
			self.weight[i] = -w
		else:
			self.id[j] = i
			self.size[i] += self.size[j]
			self.weight[j] = w

	def diff(self, p: int, q: int) -> None:
		(i, j) = (self.root(p), self.root(q))
		if i == j:
			print(self.get_weight(q) - self.get_weight(p))
		else:
			print('?')
(n, q) = map(int, input().split())
u = UnionSet(n)
for _ in range(q):
	(op, *v) = map(lambda x: int(x), input().split())
	if 0 == op:
		(x, y, w) = v
		u.unite(x, y, w)
	else:
		(x, y) = v
		u.diff(x, y)


def findSet(x):
	if x == parent[x]:
		return x
	else:
		temp = parent[x]
		parent[x] = findSet(parent[x])
		weight[x] += weight[temp]
		return parent[x]

def link(x, y, z):
	a = findSet(x)
	b = findSet(y)
	if a != b:
		parent[b] = a
		weight[b] = weight[x] - weight[y] + z
(n, q) = list(map(int, input().split()))
data = (i for i in range(n))
parent = [i for i in range(n)]
weight = [0 for i in range(n)]
for i in range(q):
	Ltemp = list(map(int, input().split()))
	if len(Ltemp) == 4:
		(x, y, z) = Ltemp[1:]
		link(x, y, z)
	else:
		(x, y) = Ltemp[1:]
		if findSet(x) == findSet(y):
			print(weight[y] - weight[x])
		else:
			print('?')


import sys
sys.setrecursionlimit(1000000)
(N, Q) = map(int, sys.stdin.readline().split())
qs = tuple((tuple(map(int, sys.stdin.readline().rstrip().split())) for _ in range(Q)))
parents = list(range(N))
diffs = [0] * N

def root(x):
	if parents[x] == x:
		return x
	else:
		pr = root(parents[x])
		diffs[x] += diffs[parents[x]]
		parents[x] = pr
		return pr

def weight(x):
	root(x)
	return diffs[x]

def diff(x, y):
	return weight(y) - weight(x)

def issame(x, y):
	return root(x) == root(y)

def merge(x, y, w):
	rx = root(x)
	ry = root(y)
	diff = weight(x) + (w - weight(y))
	if rx == ry:
		return False
	if rx < ry:
		(rx, ry, diff) = (ry, rx, -diff)
	diffs[ry] = diff
	parents[ry] = rx
	return True
for q in qs:
	if q[0] == 0:
		(x, y, z) = q[1:]
		merge(x, y, z)
	else:
		(x, y) = q[1:]
		if root(x) == root(y):
			print(diff(x, y))
		else:
			print('?')


class WeightedUnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [i for i in range(n)]
		self.rank = [0 for _ in range(n)]
		self.weight = [0 for _ in range(n)]

	def find(self, x):
		root = x
		stack = [root]
		while self.parents[root] != root:
			root = self.parents[root]
			stack.append(root)
		while stack:
			s = stack.pop()
			self.weight[s] += self.weight[self.parents[s]]
		while self.parents[x] != root:
			parent = self.parents[x]
			self.parents[x] = root
			x = parent
		return root

	def unite(self, x, y, w):
		xroot = self.find(x)
		yroot = self.find(y)
		if xroot == yroot:
			return
		xrank = self.rank[xroot]
		yrank = self.rank[yroot]
		if xrank < yrank:
			self.parents[xroot] = yroot
			self.weight[xroot] = w - self.weight[x] + self.weight[y]
		else:
			self.parents[yroot] = xroot
			self.weight[yroot] = -w + self.weight[x] - self.weight[y]
			if xrank == yrank:
				self.rank[xroot] += 1

	def diff(self, x, y):
		return self.weight[x] - self.weight[y]
(N, Q) = map(int, input().split())
uf = WeightedUnionFind(N)
ans = []
for _ in range(Q):
	q = list(map(int, input().split()))
	if q[0] == 0:
		uf.unite(q[1], q[2], q[3])
	elif uf.find(q[1]) == uf.find(q[2]):
		ans.append(uf.diff(q[1], q[2]))
	else:
		ans.append('?')
print('\n'.join(map(str, ans)))


(n, q) = map(int, input().split())
Q = [list(map(int, input().split())) for i in range(q)]
Group = [[i, 0] for i in range(n)]

def find(x):
	ANS = Group[x][1]
	while Group[x][0] != x:
		x = Group[x][0]
		ANS += Group[x][1]
	return (x, ANS)

def Union(x, y, z):
	if find(x)[0] != find(y)[0]:
		MIN = min(find(x)[0], find(y)[0])
		if find(x)[0] == MIN:
			Group[find(y)[0]] = [MIN, z + find(x)[1] - find(y)[1]]
		else:
			Group[find(x)[0]] = [MIN, -z + find(y)[1] - find(x)[1]]
for query in Q:
	if query[0] == 0:
		Union(query[1], query[2], query[3])
	elif find(query[1])[0] == find(query[2])[0]:
		print(find(query[2])[1] - find(query[1])[1])
	else:
		print('?')


from collections import defaultdict
from collections import deque
from heapq import heappush, heappop
import sys
import math
import bisect
import random

def LI():
	return list(map(int, sys.stdin.readline().split()))

def I():
	return int(sys.stdin.readline())

def LS():
	return list(map(list, sys.stdin.readline().split()))

def S():
	return list(sys.stdin.readline())[:-1]

def IR(n):
	l = [None for i in range(n)]
	for i in range(n):
		l[i] = I()
	return l

def LIR(n):
	l = [None for i in range(n)]
	for i in range(n):
		l[i] = LI()
	return l

def SR(n):
	l = [None for i in range(n)]
	for i in range(n):
		l[i] = S()
	return l

def LSR(n):
	l = [None for i in range(n)]
	for i in range(n):
		l[i] = SR()
	return l
sys.setrecursionlimit(1000000)
mod = 1000000007

def root(x):
	if par[x] == x:
		return x
	r = root(par[x])
	w[x] += w[par[x]]
	par[x] = r
	return par[x]

def same(x, y):
	return root(x) == root(y)

def unite(x, y, z):
	z += w[x] - w[y]
	x = root(x)
	y = root(y)
	if rank[x] < rank[y]:
		par[x] = y
		w[x] = -z
	else:
		par[y] = x
		w[y] = z
		if rank[x] == rank[y]:
			rank[x] += 1
(n, Q) = LI()
par = [i for i in range(n)]
rank = [0 for i in range(n)]
w = [0 for i in range(n)]
for _ in range(Q):
	q = LI()
	if q[0]:
		(x, y) = (q[1], q[2])
		if same(x, y):
			print(w[y] - w[x])
		else:
			print('?')
	else:
		(x, y, z) = q[1:]
		if not same(x, y):
			unite(x, y, z)


class Node:

	def __init__(self, root, length, dist):
		self.root = root
		self.child = None
		self.length = length
		self.dist = dist
		self.tail = root

def relate(x, y, z):
	X = A[x].root
	Y = A[y].root
	if A[X].length > A[Y].length:
		(x, y, z, X, Y) = (y, x, -z, Y, X)
	T = A[Y].tail
	A[T].child = X
	A[Y].tail = A[X].tail
	A[Y].length += A[X].length
	tmp = X
	xd = A[x].dist
	yd = A[y].dist
	for _ in range(A[X].length):
		A[tmp].root = Y
		A[tmp].dist += yd - z - xd
		tmp = A[tmp].child
(n, q) = [int(i) for i in input().split()]
A = []
for i in range(n):
	A.append(Node(i, 1, 0))
for _ in range(q):
	c = [int(i) for i in input().split()]
	if c[0] == 0 and A[c[1]].root != A[c[2]].root:
		relate(c[1], c[2], c[3])
	elif c[0] == 1:
		if A[c[1]].root == A[c[2]].root:
			print(A[c[2]].dist - A[c[1]].dist)
		else:
			print('?')


import sys
input = sys.stdin.readline

class WeightedUnionFind:

	def __init__(self, N):
		self.parent = list(range(N))
		self.rank = [0] * N
		self.weight = [0] * N

	def find(self, x):
		stack = [x]
		while self.parent[x] != x:
			x = self.parent[x]
			stack.append(x)
		r = stack[-1]
		while stack:
			x = stack.pop()
			self.weight[x] += self.weight[self.parent[x]]
			self.parent[x] = r
		return r

	def unite(self, x, y, w):
		rx = self.find(x)
		ry = self.find(y)
		w += self.weight[x] - self.weight[y]
		x = rx
		y = ry
		if x == y:
			return False
		if self.rank[x] < self.rank[y]:
			(x, y) = (y, x)
			w = -w
		if self.rank[x] == self.rank[y]:
			self.rank[x] += 1
		self.parent[y] = x
		self.weight[y] = w
		return True

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def diff(self, x, y):
		self.find(x)
		self.find(y)
		return self.weight[y] - self.weight[x]
(n, q) = map(int, input().split())
wuf = WeightedUnionFind(n)
for _ in range(q):
	(t, *data) = map(int, input().split())
	if t == 0:
		(x, y, z) = data
		wuf.unite(x, y, z)
	else:
		(x, y) = data
		if not wuf.same(x, y):
			print('?')
		else:
			print(wuf.diff(x, y))


import sys
readline = sys.stdin.readline
write = sys.stdout.write
(N, Q) = map(int, readline().split())
(*p,) = range(N)
data = [0] * N

def root(x):
	if x == p[x]:
		return x
	r = p[x]
	p[x] = y = root(r)
	data[x] += data[r]
	return y

def unite(x, y, z):
	px = root(x)
	py = root(y)
	if px == py:
		return 0
	if px < py:
		p[py] = px
		data[py] = data[x] - data[y] - z
	else:
		p[px] = py
		data[px] = data[y] - data[x] + z
	return 1
ans = []
for q in range(Q):
	(t, *cmd) = map(int, readline().split())
	if t:
		(x, y) = cmd
		px = root(x)
		py = root(y)
		if px == py:
			ans.append(str(data[x] - data[y]))
		else:
			ans.append('?')
	else:
		(x, y, z) = cmd
		unite(x, y, z)
write('\n'.join(ans))
write('\n')


class UnionFind:

	def __init__(self, n):
		self.tree = [-1] * n
		self.rank = [0] * n
		self.diff_weight = [0] * n

	def unite(self, a, b, w):
		w += self.weight(a)
		w -= self.weight(b)
		a = self.root(a)
		b = self.root(b)
		if a == b:
			return
		if self.rank[a] < self.rank[b]:
			(a, b) = (b, a)
			w *= -1
		if self.rank[a] == self.rank[b]:
			self.rank[a] += 1
		self.tree[b] = a
		self.diff_weight[b] = w

	def root(self, a):
		if self.tree[a] < 0:
			return a
		else:
			res = self.root(self.tree[a])
			self.diff_weight[a] += self.diff_weight[self.tree[a]]
			self.tree[a] = res
			return self.tree[a]

	def same(self, a, b):
		return self.root(a) == self.root(b)

	def weight(self, a):
		self.root(a)
		return self.diff_weight[a]

	def diff(self, a, b):
		return self.weight(b) - self.weight(a)
import sys
stdin = sys.stdin
na = lambda : map(int, stdin.readline().split())
ns = lambda : stdin.readline().rstrip()
ni = lambda : int(ns())

def main():
	(n, m) = na()
	uf = UnionFind(n)
	for _ in range(m):
		t = list(na())
		(a, b, c) = (t[0], t[1], t[2])
		if a == 0:
			d = t[3]
			uf.unite(b, c, d)
		elif uf.same(b, c):
			print(uf.diff(b, c))
		else:
			print('?')
main()


import sys
readline = sys.stdin.buffer.readline
(n, q) = map(int, readline().split())

class WeightedUnionFind:

	def __init__(self, n):
		self.par = [i for i in range(n + 1)]
		self.rank = [0] * (n + 1)
		self.weight = [0] * (n + 1)

	def find(self, x):
		if self.par[x] == x:
			return x
		else:
			y = self.find(self.par[x])
			self.weight[x] += self.weight[self.par[x]]
			self.par[x] = y
			return y

	def union(self, x, y, w):
		rx = self.find(x)
		ry = self.find(y)
		if self.rank[rx] < self.rank[ry]:
			self.par[rx] = ry
			self.weight[rx] = w - self.weight[x] + self.weight[y]
		else:
			self.par[ry] = rx
			self.weight[ry] = -w - self.weight[y] + self.weight[x]
			if self.rank[rx] == self.rank[ry]:
				self.rank[rx] += 1

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def diff(self, x, y):
		return self.weight[x] - self.weight[y]
uf = WeightedUnionFind(n)
for i in range(q):
	s = readline().rstrip().decode('utf-8')
	if s[0] == '0':
		(com, a, b, w) = map(int, s.split())
	else:
		(com, a, b) = map(int, s.split())
	if com:
		if uf.same(a, b):
			print(uf.diff(a, b))
		else:
			print('?')
	else:
		uf.union(a, b, w)


import sys
sys.setrecursionlimit(50000)

def findset(x, element, weight):
	if x != element[x]:
		(element[x], s) = findset(element[x], element, weight)
		weight[x] += s
	return (element[x], weight[x])

def union(x, y, element, weight, w):
	(x, wx) = findset(x, element, weight)
	(y, wy) = findset(y, element, weight)
	link(x, wx, y, wy, w, element)

def link(x, wx, y, wy, z, element):
	z = z + wy - wx
	element[x] = y
	weight[x] = z
(n, q) = map(int, input().split())
element = [i for i in range(n)]
weight = [0 for i in range(n)]
for i in range(q):
	inp = map(int, input().split())
	inp = list(inp)
	if inp[0] == 0:
		(x, y, w) = (inp[1], inp[2], inp[3])
		union(x, y, element, weight, w)
	else:
		(x, y) = (inp[1], inp[2])
		(x, wx) = findset(x, element, weight)
		(y, wy) = findset(y, element, weight)
		if x == y:
			print(wx - wy)
		else:
			print('?')


import sys

class WeightedUnionFind:

	def __init__(self, N: int):
		self.par = [i for i in range(N)]
		self.rank = [0] * N
		self.weight = [0] * N

	def root(self, x: int) -> int:
		if self.par[x] == x:
			return x
		else:
			root_x = self.root(self.par[x])
			self.weight[x] += self.weight[self.par[x]]
			self.par[x] = root_x
			return root_x

	def get_weight(self, x: int) -> int:
		self.root(x)
		return self.weight[x]

	def get_diff(self, x: int, y: int) -> int:
		if not self.is_same(x, y):
			return '?'
		return self.get_weight(y) - self.get_weight(x)

	def is_same(self, x: int, y: int) -> bool:
		return self.root(x) == self.root(y)

	def unite(self, x: int, y: int, w: int) -> bool:
		if self.is_same(x, y):
			return False
		root_x = self.root(x)
		root_y = self.root(y)
		w += self.weight[x] - self.weight[y]
		if self.rank[root_x] < self.rank[root_y]:
			self.par[root_x] = root_y
			self.weight[root_x] = -w
		else:
			self.par[root_y] = root_x
			self.weight[root_y] = w
			if self.rank[root_x] == self.rank[root_y]:
				self.rank[root_x] += 1
		return True

	def get_size(self, x: int) -> int:
		return self.size[self.root(x)]
(N, Q) = map(int, sys.stdin.readline().split())
(UNITE, DIFF) = (0, 1)
wuf = WeightedUnionFind(N)
for _ in range(Q):
	(com, *x) = map(int, sys.stdin.readline().split())
	if com == UNITE:
		wuf.unite(*x)
	else:
		print(wuf.get_diff(*x))


class WeightedUnionFind:

	def __init__(self, n):
		self.n = n
		self.nodes = [i for i in range(n)]
		self.weights = [0 for _ in range(n)]
		self.size = [1 for _ in range(n)]

	def connected(self, i, j):
		(wi, ri) = self.root(i)
		(wj, rj) = self.root(j)
		return ri == rj

	def union(self, i, j, w):
		(wi, ri) = self.root(i)
		(wj, rj) = self.root(j)
		if ri == rj:
			return
		if self.size[ri] < self.size[rj]:
			self.nodes[ri] = rj
			self.weights[ri] = wj - wi + w
			self.size[rj] += self.size[ri]
		else:
			self.nodes[rj] = ri
			self.weights[rj] = wi - wj - w
			self.size[ri] += self.size[rj]

	def weight(self, i, j):
		(wi, ri) = self.root(i)
		(wj, rj) = self.root(j)
		if ri != rj:
			raise ValueError('{} and {} is not connected'.format(i, j))
		return wi - wj

	def root(self, i):
		r = self.nodes[i]
		w = self.weights[i]
		while r != self.nodes[r]:
			w += self.weights[r]
			r = self.nodes[r]
		return (w, r)

def run():
	(n, q) = [int(i) for i in input().split()]
	wuf = WeightedUnionFind(n)
	for _ in range(q):
		(com, *args) = input().split()
		if com == '0':
			wuf.union(*[int(i) for i in args])
		elif com == '1':
			(x, y) = [int(i) for i in args]
			if wuf.connected(x, y):
				print(wuf.weight(x, y))
			else:
				print('?')
run()


class WeightedUnionFind:
	root_position = 0

	def __init__(self, size: int):
		self.size = size
		self.parent = [-1] * size
		self.weight = [WeightedUnionFind.root_position] * size

	def find(self, x: int) -> (int, int):
		if self.parent[x] < 0:
			return (x, self.weight[x])
		same_group_items = []
		y = x
		while self.parent[y] >= 0:
			same_group_items.append(y)
			y = self.parent[y]
		w = 0
		while same_group_items:
			child = same_group_items.pop()
			w += self.weight[child]
			self.weight[child] = w
			self.parent[child] = y
		return (y, self.weight[x])

	def unite(self, x: int, y: int, weight: int) -> bool:
		(root_x, weight_x) = self.find(x)
		(root_y, weight_y) = self.find(y)
		if root_x == root_y:
			return False
		if self.parent[root_x] >= self.parent[root_y]:
			self.parent[root_x] += self.parent[root_y]
			self.parent[root_y] = root_x
			self.weight[root_y] = weight_x + weight - weight_y
		else:
			self.parent[root_y] += self.parent[root_x]
			self.parent[root_x] = root_y
			self.weight[root_x] = weight_y - weight - weight_x
		return True

def solve():
	(n, q) = map(int, input().split())
	uf = WeightedUnionFind(n)
	for _ in range(q):
		query = list(map(int, input().split()))
		if query[0] == 0:
			(_, x, y, z) = query
			uf.unite(x, y, z)
		else:
			(_, x, y) = query
			(root_x, pos_x) = uf.find(x)
			(root_y, pos_y) = uf.find(y)
			if root_x == root_y:
				print(pos_y - pos_x)
			else:
				print('?')
solve()


import sys
sys.setrecursionlimit(1000000000)
iim = lambda : map(int, input().rstrip().split())

def resolve():
	(N, Q) = iim()
	S = [i for i in range(N)]
	W = [0] * N
	R = [0] * N

	def find(x):
		if S[x] == x:
			return x
		i = find(S[x])
		W[x] += W[S[x]]
		S[x] = i
		return i

	def relate(x, y, w):
		i = find(x)
		j = find(y)
		if R[i] < R[j]:
			S[i] = j
			W[i] = w - W[x] + W[y]
		else:
			S[j] = i
			W[j] = -w - W[y] + W[x]
			if R[i] == R[j]:
				R[i] += 1

	def diff(x, y):
		i = find(x)
		j = find(y)
		if i != j:
			return '?'
		return W[x] - W[y]
	ans = []
	for (com, *xyz) in (map(int, s.split()) for s in sys.stdin):
		if com == 0:
			relate(*xyz)
		else:
			ans.append(diff(*xyz))
	print(*ans, sep='\n')
resolve()


import sys
input = sys.stdin.readline

class UnionFindWeighted:

	def __init__(self, n):
		self.parent = [-1] * n
		self.weight = [0] * n
		self.cnt = n
		self.INF = 10 ** 18

	def root(self, x):
		if self.parent[x] < 0:
			return x
		rt = self.root(self.parent[x])
		self.weight[x] += self.weight[self.parent[x]]
		self.parent[x] = rt
		return rt

	def merge(self, x, y, weight):
		root_x = self.root(x)
		root_y = self.root(y)
		if root_x == root_y:
			return
		if self.parent[root_x] < self.parent[root_y]:
			self.parent[root_x] += self.parent[root_y]
			self.parent[root_y] = root_x
			self.weight[root_y] = -weight + self.weight[x] - self.weight[y]
		else:
			self.parent[root_y] += self.parent[root_x]
			self.parent[root_x] = root_y
			self.weight[root_x] = weight - self.weight[x] + self.weight[y]
		self.cnt -= 1

	def is_same(self, x, y):
		return self.root(x) == self.root(y)

	def diff(self, x, y):
		if not self.is_same(x, y):
			return self.INF
		return self.weight[x] - self.weight[y]

	def get_size(self, x):
		return -self.parent[self.root(x)]

	def get_cnt(self):
		return self.cnt

def solve():
	(n, q) = list(map(int, input().split()))
	query = [list(map(int, input().split())) for i in range(q)]
	INF = 10 ** 18
	ufw = UnionFindWeighted(n)
	for i in range(q):
		if query[i][0] == 0:
			(_, x, y, weight) = query[i]
			ufw.merge(x, y, weight)
		else:
			(_, x, y) = query[i]
			ans = ufw.diff(x, y)
			if ans == INF:
				print('?')
			else:
				print(ans)
solve()


class WeightedUnionFind:

	def __init__(self, N):
		self.vertices = [-1 for _ in range(N)]
		self.rank = [0] * N
		self.diff_weight = [0] * N

	def weight(self, v):
		self.find(v)
		return self.diff_weight[v]

	def dist(self, u, v):
		if self.is_connected(x, y):
			return self.weight(v) - self.weight(u)
		else:
			return None

	def find(self, v):
		if self.vertices[v] < 0:
			return v
		else:
			root = self.find(self.vertices[v])
			self.diff_weight[v] += self.diff_weight[self.vertices[v]]
			self.vertices[v] = root
			return self.vertices[v]

	def union(self, u, v, w):
		s1 = self.find(u)
		s2 = self.find(v)
		if s1 == s2:
			return False
		if self.rank[s1] > self.rank[s2]:
			self.vertices[s1] += self.vertices[s2]
			self.vertices[s2] = s1
			self.diff_weight[s2] = w - self.diff_weight[v] + self.diff_weight[u]
		else:
			self.vertices[s2] += self.vertices[s1]
			self.vertices[s1] = s2
			self.diff_weight[s1] = -w - self.diff_weight[u] + self.diff_weight[v]
			if self.rank[s1] == self.rank[s2]:
				self.rank[s2] += 1
		return True

	def is_connected(self, u, v):
		return self.find(u) == self.find(v)

	def size(self, v):
		return -self.vertices[self.find(v)]
(n, q) = map(int, input().split())
wuf = WeightedUnionFind(n)
for _ in range(q):
	txt = input().split()
	if txt[0] == '0':
		(x, y, z) = map(int, txt[1:])
		wuf.union(x, y, z)
	else:
		(x, y) = map(int, txt[1:])
		res = wuf.dist(x, y)
		if res is not None:
			print(res)
		else:
			print('?')


import sys
from sys import stdin, setrecursionlimit
sys.setrecursionlimit(10 ** 6)
input = stdin.readline

class Weighted_Union_find:

	def __init__(self, n):
		self.n = n
		self.root = [i for i in range(n + 1)]
		self.rank = [0] * (n + 1)
		self.weight = [0] * (n + 1)

	def find_root(self, x):
		if self.root[x] == x:
			return x
		else:
			y = self.find_root(self.root[x])
			self.weight[x] += self.weight[self.root[x]]
			self.root[x] = y
			return y

	def unite(self, x, y, w):
		rx = self.find_root(x)
		ry = self.find_root(y)
		if rx == ry:
			return 0
		if self.rank[rx] < self.rank[ry]:
			self.root[rx] = ry
			self.weight[rx] = w - self.weight[x] + self.weight[y]
		else:
			self.root[ry] = rx
			self.weight[ry] = -w - self.weight[y] + self.weight[x]
			if self.rank[rx] == self.rank[ry]:
				self.rank[rx] += 1

	def same(self, x, y):
		return self.find_root(x) == self.find_root(y)

	def count(self, x):
		return -self.root[self.find_root(x)]

	def diff(self, x, y):
		return self.weight[x] - self.weight[y]

def main():
	(n, q) = map(int, input().split())
	gragh = Weighted_Union_find(n + 1)
	for _ in range(q):
		query = tuple(map(int, input().split()))
		if not query[0]:
			gragh.unite(query[1], query[2], query[3])
		elif gragh.same(query[1], query[2]):
			print(gragh.diff(query[1], query[2]))
		else:
			print('?')
main()


[p, rank] = [[], []]
[n, q] = list(map(int, input().split()))
w = [0 for i in range(n)]

def findSet(x):
	global p
	if x != p[x]:
		pre_parent = p[x]
		p[x] = findSet(p[x])
		w[x] += w[pre_parent]
	return p[x]

def makeSet(x):
	global p, rank
	p.append(x)
	rank.append(0)

def link(x, y, z):
	global p, rank, w
	if rank[x] > rank[y]:
		p[y] = x
		w[y] = z
	else:
		p[x] = y
		w[x] = -z
		if rank[x] == rank[y]:
			rank[y] += 1

def relate(x, y, z):
	global w
	root_x = findSet(x)
	root_y = findSet(y)
	z += w[x]
	z -= w[y]
	link(root_x, root_y, z)

def same(x, y):
	if findSet(x) == findSet(y):
		return 1
	else:
		return 0

def difference(x, y):
	if same(x, y) == 1:
		global w
		return w[y] - w[x]
	else:
		return '?'
for i in range(n):
	makeSet(i)
for i in range(q):
	data = list(map(int, input().split()))
	if data[0] == 0:
		relate(data[1], data[2], data[3])
	else:
		print(difference(data[1], data[2]))


idx_com = 0
idx_x = 1
idx_y = 2
idx_z = 3
(n, nq) = map(int, input().split())
parents = list(range(n))
ranks = [0] * n
weights = [0] * n

def find(x):
	if parents[x] == x:
		return x
	y = find(parents[x])
	weights[x] += weights[parents[x]]
	parents[x] = y
	return y

def unite(x, y, w):
	root_x = find(x)
	root_y = find(y)
	if ranks[root_x] < ranks[root_y]:
		parents[root_x] = root_y
		weights[root_x] = -w - weights[x] + weights[y]
	else:
		parents[root_y] = root_x
		weights[root_y] = +w - weights[y] + weights[x]
		if ranks[root_x] == ranks[root_y]:
			ranks[root_x] += 1

def same(x, y):
	return find(x) == find(y)

def diff(x, y):
	return weights[y] - weights[x]
for _ in range(nq):
	q = list(map(int, input().split()))
	if q[idx_com] == 0:
		unite(q[idx_x], q[idx_y], q[idx_z])
	elif same(q[idx_x], q[idx_y]):
		print(diff(q[idx_x], q[idx_y]))
	else:
		print('?')


import sys
sys.setrecursionlimit(10000000)
input = sys.stdin.readline

def diff(node_a, node_b):
	(root_a, cost_a) = node_a.root(0)
	(root_b, cost_b) = node_b.root(0)
	if root_a == root_b:
		return (True, cost_a - cost_b)
	else:
		return (False, cost_a - cost_b)

def merge(node_a, node_b, weight):
	(root_a, cost_a) = node_a.root(0)
	(root_b, cost_b) = node_b.root(0)
	root_a.weight = weight + cost_b - cost_a
	root_a.is_root = False
	root_a.parent = root_b

class Node:

	def __init__(self):
		self.is_root = True
		self.parent = None
		self.weight = 0

	def root(self, cost):
		if self.is_root:
			return (self, cost)
		(root, max_) = self.parent.root(cost + self.weight)
		self.parent = root
		self.weight = max_ - cost
		return (root, max_)

def main():
	(n, q) = map(int, input().split())
	nodes = [Node() for i in range(n)]
	for i in range(q):
		query = list(map(int, input().split()))
		if query[0] == 0:
			(x, y, z) = query[1:]
			if not diff(nodes[x], nodes[y])[0]:
				merge(nodes[x], nodes[y], z)
		else:
			(x, y) = query[1:]
			(jud, ans) = diff(nodes[x], nodes[y])
			if jud:
				print(ans)
			else:
				print('?')
main()


import sys
sys.setrecursionlimit(50000)

def findset(x, element, weight):
	if x != element[x]:
		(element[x], s) = findset(element[x], element, weight)
		weight[x] += s
	return (element[x], weight[x])

def union(x, y, element, weight, w):
	(x, wx) = findset(x, element, weight)
	(y, wy) = findset(y, element, weight)
	link(x, y, w + wy - wx, element, weight)

def link(x, y, z, element, weight):
	element[x] = y
	weight[x] = z
(n, q) = map(int, input().split())
element = [i for i in range(n)]
weight = [0 for i in range(n)]
for i in range(q):
	inp = map(int, input().split())
	inp = list(inp)
	if inp[0] == 0:
		(x, y, w) = (inp[1], inp[2], inp[3])
		union(x, y, element, weight, w)
	else:
		(x, y) = (inp[1], inp[2])
		(x, wx) = findset(x, element, weight)
		(y, wy) = findset(y, element, weight)
		if x == y:
			print(wx - wy)
		else:
			print('?')


class UnionFind:

	def __init__(self, s):
		self.nodes = [-1 for _ in range(s)]
		self.rank = [0 for _ in range(s)]
		self.weight = [0 for _ in range(s)]

	def find(self, x):
		if self.nodes[x] < 0:
			return x
		else:
			root = self.find(self.nodes[x])
			self.weight[x] += self.weight[self.nodes[x]]
			self.nodes[x] = root
			return root

	def union(self, x, y, z):
		s_1 = self.find(x)
		s_2 = self.find(y)
		if s_1 == s_2:
			return False
		if self.rank[s_1] < self.rank[s_2]:
			self.nodes[s_1] = s_2
			self.weight[s_1] = z - self.weight[x] + self.weight[y]
		elif self.rank[s_1] > self.rank[s_2]:
			self.nodes[s_2] = s_1
			self.weight[s_2] = self.weight[x] - self.weight[y] - z
		else:
			self.nodes[s_2] = s_1
			self.weight[s_2] = self.weight[x] - self.weight[y] - z
			self.rank[s_1] += 1
		return True

	def is_same(self, x, y):
		return self.find(x) == self.find(y)

	def diff(self, x, y):
		return self.weight[x] - self.weight[y]
(n, q) = map(int, input().split())
sets = UnionFind(n)
for i in range(q):
	inputs = [int(i) for i in input().split()]
	if inputs[0] == 0:
		sets.union(inputs[1], inputs[2], inputs[3])
	elif sets.is_same(inputs[1], inputs[2]):
		print(sets.diff(inputs[1], inputs[2]))
	else:
		print('?')


import sys
sys.setrecursionlimit(50000)

def findSet(x, element, weight):
	if x != element[x]:
		(element[x], s) = findSet(element[x], element, weight)
		weight[x] += s
	return (element[x], weight[x])

def union(x, y, element, weight, w):
	(x, wx) = findSet(x, element, weight)
	(y, wy) = findSet(y, element, weight)
	link(x, wx, y, wy, w, element)

def link(x, wx, y, wy, z, element):
	z = z + wy - wx
	element[x] = y
	weight[x] = z
(n, q) = map(int, input().split())
element = [i for i in range(n)]
weight = [0 for i in range(n)]
for i in range(q):
	inp = map(int, input().split())
	inp = list(inp)
	if inp[0] == 0:
		(x, y, w) = (inp[1], inp[2], inp[3])
		union(x, y, element, weight, w)
	else:
		(x, y) = (inp[1], inp[2])
		(x, wx) = findSet(x, element, weight)
		(y, wy) = findSet(y, element, weight)
		if x == y:
			print(wx - wy)
		else:
			print('?')


MAX = 100003
ws = [0 for i in range(MAX)]
par = [-1 for i in range(MAX * 2)]

def weight(x):
	root(x)
	return ws[x]

def root(x):
	if par[x] < 0:
		return x
	p = root(par[x])
	ws[x] = ws[x] + ws[par[x]]
	par[x] = p
	return par[x]

def unite(y, x, z):
	z = z + weight(x)
	z = z - weight(y)
	x = root(x)
	y = root(y)
	if x == y:
		return 0
	if par[y] < par[x]:
		tmp = x
		x = y
		y = tmp
		z = -z
	par[x] = par[x] + par[y]
	par[y] = x
	ws[y] = z
	return 1

def diff(x, y):
	if root(x) != root(y):
		return 0
	ans = ws[x] - ws[y]
	return (1, ans)
global ans
(N, Q) = (int(x) for x in input().split())
for q in range(Q):
	(t, *cmd) = (int(x) for x in input().split())
	if t:
		(x, y) = cmd
		z = diff(x, y)
		if z == False:
			print('?')
		else:
			print(z[1])
	else:
		(x, y, z) = cmd
		unite(x, y, z)


class WUnionFind:
	MAX_N = 0
	PAR = []
	RANK = []
	DIFF_WEIGHT = []

	def __init__(self, n):
		self.MAX_N = n
		self.PAR = [i for i in range(n)]
		self.RANK = [0] * n
		self.DIFF_WEIGHT = [0] * n

	def root(self, x):
		if self.PAR[x] == x:
			return x
		else:
			r = self.root(self.PAR[x])
			self.DIFF_WEIGHT[x] += self.DIFF_WEIGHT[self.PAR[x]]
			self.PAR[x] = r
			return r

	def weight(self, x):
		self.root(x)
		return self.DIFF_WEIGHT[x]

	def issame(self, x, y):
		return self.root(x) == self.root(y)

	def diff(self, x, y):
		return self.weight(y) - self.weight(x)

	def merge(self, x, y, w):
		w += self.weight(x)
		w -= self.weight(y)
		x = self.root(x)
		y = self.root(y)
		if x == y:
			return False
		if self.RANK[x] < self.RANK[y]:
			(x, y) = (y, x)
			w = -w
		if self.RANK[x] == self.RANK[y]:
			self.RANK[x] += 1
		self.PAR[y] = x
		self.DIFF_WEIGHT[y] = w
		return True
(N, Q) = map(int, input().split())
Ans = []
tree = WUnionFind(N + 1)
for _ in range(Q):
	s = list(map(int, input().split()))
	if s[0] == 0:
		x = s[1]
		y = s[2]
		w = s[3]
		tree.merge(x, y, w)
	else:
		x = s[1]
		y = s[2]
		if tree.issame(x, y):
			a = tree.diff(x, y)
		else:
			a = '?'
		Ans.append(a)
for a in Ans:
	print(a)


class WeightedUnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n
		self.differ = [0] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			r = self.find(self.parents[x])
			self.differ[x] += self.differ[self.parents[x]]
			self.parents[x] = r
			return self.parents[x]

	def unite(self, x, y, w):
		xp = self.find(x)
		yp = self.find(y)
		w = w + self.differ[x] - self.differ[y]
		if xp != yp:
			if self.parents[xp] > self.parents[yp]:
				(xp, yp) = (yp, xp)
				w = -w
			self.parents[xp] += self.parents[yp]
			self.parents[yp] = xp
			self.differ[yp] = w

	def diff(self, x, y):
		xp = self.find(x)
		yp = self.find(y)
		if xp == yp:
			return (True, self.differ[y] - self.differ[x])
		else:
			return (False, 0)
(n, q) = map(int, input().split())
WUF = WeightedUnionFind(n)
for i in range(q):
	query = list(map(int, input().split()))
	if query[0] == 0:
		WUF.unite(query[1], query[2], query[3])
	elif query[0] == 1:
		(judge, diff) = WUF.diff(query[1], query[2])
		if judge:
			print(diff)
		else:
			print('?')


import math

class weightedUnionTree:

	def __init__(self, n):
		self.par = [i for i in range(n + 1)]
		self.rank = [0] * (n + 1)
		self.weight = [0] * (n + 1)

	def find(self, x):
		if self.par[x] == x:
			return x
		else:
			y = self.find(self.par[x])
			self.weight[x] += self.weight[self.par[x]]
			self.par[x] = y
			return y

	def union(self, x, y, w):
		rx = self.find(x)
		ry = self.find(y)
		if self.rank[rx] < self.rank[ry]:
			self.par[rx] = ry
			self.weight[rx] = w - self.weight[x] + self.weight[y]
		else:
			self.par[ry] = rx
			self.weight[ry] = -w - self.weight[y] + self.weight[x]
			if self.rank[rx] == self.rank[ry]:
				self.rank[rx] += 1

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def diff(self, x, y):
		if self.same(x, y):
			print(self.weight[x] - self.weight[y])
		else:
			print('?')
(n, q) = (int(p) for p in input().split())
tree = weightedUnionTree(n)
for i in range(q):
	(question, num) = input().split(' ', 1)
	if int(question) == 0:
		(x, y, z) = num.split(' ')
		tree.union(int(x), int(y), int(z))
	else:
		(x, y) = num.split(' ')
		tree.diff(int(x), int(y))


par = [i for i in range(100)]
rank = [0 for i in range(100)]
weight_to_root = [0 for i in range(100)]

def find(x):
	if par[x] == x:
		return x
	else:
		root = find(par[x])
		weight_to_root[x] += weight_to_root[par[x]]
		par[x] = root
		return root

def is_same(x, y):
	return find(x) == find(y)

def union(x, y, w):
	a = find(x)
	b = find(y)
	if a == b:
		return False
	if rank[a] > rank[b]:
		par[b] = a
		weight_to_root[b] = -w - weight_to_root[y] + weight_to_root[x]
	elif rank[a] < rank[b]:
		par[a] = b
		weight_to_root[a] = w - weight_to_root[x] + weight_to_root[y]
	else:
		par[b] = a
		weight_to_root[b] = -w - weight_to_root[y] + weight_to_root[x]
		rank[a] += 1
	return True

def diff(x, y):
	return weight_to_root[x] - weight_to_root[y]

def main():
	global rank, par, weight_to_root
	(n, q) = [int(i) for i in input().split()]
	rank = [0 for i in range(n)]
	par = [i for i in range(n)]
	weight_to_root = [0 for i in range(n)]
	for i in range(q):
		s = [int(j) for j in input().split()]
		if s[0] == 0:
			union(s[1], s[2], s[3])
		elif is_same(s[1], s[2]):
			print(diff(s[1], s[2]))
		else:
			print('?')
main()


class WeightedUnionFindTree:

	def __init__(self, n_nodes):
		super().__init__()
		self.par = [i for i in range(n_nodes)]
		self.rank = [0] * n_nodes
		self.weights = [0] * n_nodes

	def find(self, x):
		if self.par[x] == x:
			return x
		root = self.find(self.par[x])
		self.weights[x] += self.weights[self.par[x]]
		self.par[x] = root
		return root

	def unite(self, x, y, weight=0):
		(root_x, root_y) = (self.find(x), self.find(y))
		if root_x == root_y:
			return
		if self.rank[root_x] < self.rank[root_y]:
			self.par[root_x] = root_y
			self.weights[root_x] = weight - self.weights[x] + self.weights[y]
		else:
			self.par[root_y] = root_x
			self.weights[root_y] = -(weight - self.weights[x] + self.weights[y])
			if self.rank[root_x] == self.rank[root_y]:
				self.rank[root_x] += 1

	def is_same(self, x, y):
		return self.find(x) == self.find(y)

	def diff(self, x, y):
		return self.weights[x] - self.weights[y]
(N, Q) = map(int, input().split())
uft = WeightedUnionFindTree(N)
ans = []
for _ in range(Q):
	(type_, u, v, *weight) = map(int, input().split())
	if type_ == 0:
		uft.unite(u, v, weight[0])
	else:
		ans.append(uft.diff(u, v) if uft.is_same(u, v) else '?')
for a in ans:
	print(a)


(n, q) = map(int, input().split())
p = [i for i in range(n)]
rank = [0 for i in range(n)]
diff_weight = [0 for i in range(n)]

def link(x, y, z):
	if rank[x] > rank[y]:
		p[y] = x
		diff_weight[y] = z
	else:
		p[x] = y
		diff_weight[x] = -z
		if rank[x] == rank[y]:
			rank[y] = rank[y] + 1

def findSet(x):
	if x != p[x]:
		parent = p[x]
		p[x] = findSet(p[x])
		diff_weight[x] += diff_weight[parent]
	return p[x]

def relate(x, y, z):
	rootx = findSet(x)
	rooty = findSet(y)
	z += diff_weight[x]
	z -= diff_weight[y]
	if rootx != rooty:
		link(rootx, rooty, z)

def same(x, y):
	x = findSet(x)
	y = findSet(y)
	if x == y:
		return 1
	else:
		return 0

def weight(x):
	findSet(x)
	return diff_weight[x]

def diff(x, y):
	if same(x, y) != 1:
		return '?'
	else:
		return diff_weight[y] - diff_weight[x]
output = []
for i in range(q):
	line = list(map(int, input().split()))
	if line[0] == 0:
		relate(line[1], line[2], line[3])
	else:
		output.append(diff(line[1], line[2]))
for ans in output:
	print(ans)


rank = []
p = []
w = []

def makeSet(x):
	p.append(x)
	rank.append(0)
	w.append(0)

def union(x, y):
	link(findSet(x), findSet(y))

def link(x, y):
	if rank[x] > rank[y]:
		p[y] = x
	else:
		p[x] = y
		if rank[x] == rank[y]:
			rank[y] = rank[y] + 1

def findSet(x):
	if p[x] == x:
		return x
	i = findSet(p[x])
	w[x] += w[p[x]]
	p[x] = i
	return i

def same(x, y):
	return findSet(x) == findSet(y)

def relate(x, y, z):
	i = findSet(x)
	j = findSet(y)
	if rank[i] < rank[j]:
		p[i] = j
		w[i] = z - w[x] + w[y]
	else:
		p[j] = i
		w[j] = -z - w[y] + w[x]
		if rank[i] == rank[j]:
			rank[i] += 1
(n, q) = map(int, input().split())
for i in range(n):
	makeSet(i)
for i in range(q):
	(com, *cmd) = map(int, input().split())
	if com == 0:
		relate(cmd[0], cmd[1], cmd[2])
	else:
		(x, y) = cmd
		if same(x, y):
			print(w[x] - w[y])
		else:
			print('?')


def root(x):
	if par[x] == x:
		return x
	else:
		r = root(par[x])
		diff_weight[x] += diff_weight[par[x]]
		par[x] = r
		return r

def weight(x):
	root(x)
	return diff_weight[x]

def diff(x, y):
	return weight(y) - weight(x)

def relate(a, b, w):
	w += weight(a)
	w -= weight(b)
	a = root(a)
	b = root(b)
	if a == b:
		return False
	if rank[a] < rank[b]:
		(a, b) = (b, a)
		w = -w
	if rank[a] == rank[b]:
		rank[b] += 1
		par[b] = a
	par[b] = a
	diff_weight[b] = w
	siz[a] += siz[b]
	return True

def size(a):
	return siz[root(a)]

def same(a, b):
	return root(a) == root(b)
(N, Q) = map(int, input().split())
par = [i for i in range(N)]
siz = [1 for _ in range(N)]
rank = [0 for _ in range(N)]
diff_weight = [0 for _ in range(N)]
for i in range(Q):
	query = list(map(int, input().split()))
	if not query[0]:
		(X, Y, W) = query[1:]
		relate(X, Y, W)
	else:
		(X, Y) = query[1:]
		if same(X, Y):
			print(diff(X, Y))
		else:
			print('?')


import sys
input = sys.stdin.readline

class Union:

	def __init__(self, n):
		self.par = [i for i in range(n + 1)]
		self.rank = [0] * (n + 1)
		self.weight = [0] * (n + 1)

	def find(self, x):
		if self.par[x] == x:
			return x
		y = self.find(self.par[x])
		self.weight[x] += self.weight[self.par[x]]
		self.par[x] = y
		return y

	def union(self, x, y, w):
		rx = self.find(x)
		ry = self.find(y)
		if self.rank[rx] < self.rank[ry]:
			self.par[rx] = ry
			self.weight[rx] = w - self.weight[x] + self.weight[y]
		else:
			self.par[ry] = rx
			self.weight[ry] = -w - self.weight[y] + self.weight[x]
			if self.rank[rx] == self.rank[ry]:
				self.rank[rx] += 1

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def diff(self, x, y):
		if self.same(x, y):
			return self.weight[x] - self.weight[y]
		else:
			return '?'
(n, q) = map(int, input().split())
unite = Union(n)
for i in range(q):
	(a, b, c, *d) = map(int, input().split())
	if a == 0:
		unite.union(b, c, d[0])
	else:
		print(unite.diff(b, c))


import sys
input = sys.stdin.buffer.readline

class WeightedUnionFindTree:

	def __init__(self, n_nodes):
		self.par = [i for i in range(n_nodes)]
		self.rank = [0] * n_nodes
		self.weights = [0] * n_nodes

	def find(self, x):
		if self.par[x] == x:
			return x
		root = self.find(self.par[x])
		self.weights[x] += self.weights[self.par[x]]
		self.par[x] = root
		return root

	def unite(self, x, y, weight=0):
		(root_x, root_y) = (self.find(x), self.find(y))
		if root_x == root_y:
			return
		if self.rank[root_x] < self.rank[root_y]:
			self.par[root_x] = root_y
			self.weights[root_x] = weight - self.weights[x] + self.weights[y]
		else:
			self.par[root_y] = root_x
			self.weights[root_y] = -weight - self.weights[y] + self.weights[x]
			if self.rank[root_x] == self.rank[root_y]:
				self.rank[root_x] += 1

	def is_same(self, x, y):
		return self.find(x) == self.find(y)

	def diff(self, x, y):
		return self.weights[x] - self.weights[y]

def aoj():
	(N, Q) = map(int, input().split())
	uft = WeightedUnionFindTree(N)
	ans = []
	for _ in range(Q):
		(type_, u, v, *weight) = map(int, input().split())
		if type_ == 0:
			uft.unite(u, v, weight[0])
		else:
			ans.append(uft.diff(u, v) if uft.is_same(u, v) else '?')
	print('\n'.join(map(str, ans)))
aoj()


MAX = 100005
ws = [0 for i in range(MAX)]
par = [-1 for i in range(MAX * 2)]

def weight(x):
	root(x)
	return ws[x]

def root(x):
	if par[x] < 0:
		return x
	p = root(par[x])
	ws[x] = ws[x] + ws[par[x]]
	par[x] = p
	return par[x]

def unite(y, x, z):
	z = z + weight(x)
	z = z - weight(y)
	x = root(x)
	y = root(y)
	if x == y:
		return 0
	if par[y] < par[x]:
		tmp = x
		x = y
		y = tmp
		z = -z
	par[x] = par[x] + par[y]
	par[y] = x
	ws[y] = z
	return 1

def diff(x, y):
	if root(x) != root(y):
		return 0
	ans = ws[x] - ws[y]
	return (1, ans)
global ans
(N, Q) = (int(x) for x in input().split())
for q in range(Q):
	(t, *cmd) = (int(x) for x in input().split())
	if t:
		(x, y) = cmd
		z = diff(x, y)
		if z == False:
			print('?')
		else:
			print(z[1])
	else:
		(x, y, z) = cmd
		unite(x, y, z)


[p, rank] = [[], []]
[n, q] = list(map(int, input().split()))
w = [0 for i in range(n)]

def makeSet(x):
	global p, rank
	p.append(x)
	rank.append(0)

def findSet(x):
	global p
	if x != p[x]:
		pre_parent = p[x]
		p[x] = findSet(p[x])
		w[x] += w[pre_parent]
	return p[x]

def link(x, y, z):
	global p, rank, w
	if rank[x] > rank[y]:
		p[y] = x
		w[y] = z
	else:
		p[x] = y
		w[x] = -z
		if rank[x] == rank[y]:
			rank[y] += 1

def same(x, y):
	if findSet(x) == findSet(y):
		return 1
	else:
		return 0

def relate(x, y, z):
	global w
	root_x = findSet(x)
	root_y = findSet(y)
	z += w[x]
	z -= w[y]
	link(root_x, root_y, z)

def diff(x, y):
	if same(x, y) == 1:
		global w
		return w[y] - w[x]
	else:
		return '?'
for i in range(n):
	makeSet(i)
for i in range(q):
	data = list(map(int, input().split()))
	if data[0] == 0:
		relate(data[1], data[2], data[3])
	else:
		print(diff(data[1], data[2]))


import sys
import math
from bisect import bisect_right as br
from bisect import bisect_left as bl
sys.setrecursionlimit(1000000000)
from heapq import heappush, heappop, heappushpop
from collections import defaultdict
from itertools import accumulate
from collections import Counter
from collections import deque
from operator import itemgetter
from itertools import permutations
mod = 10 ** 9 + 7
inf = float('inf')

def I():
	return int(sys.stdin.readline())

def LI():
	return list(map(int, sys.stdin.readline().split()))

def root(x):
	if par[x] == x:
		return x
	else:
		r = root(par[x])
		weight[x] += weight[par[x]]
		par[x] = r
		return r

def union(x, y, w):
	rx = root(x)
	ry = root(y)
	if rank[rx] < rank[ry]:
		par[rx] = ry
		weight[rx] = w - weight[x] + weight[y]
	else:
		par[ry] = rx
		weight[ry] = -w - weight[y] + weight[x]
		if rank[rx] == rank[ry]:
			rank[rx] += 1

def same(x, y):
	return root(x) == root(y)

def diff(x, y):
	return weight[x] - weight[y]
(n, q) = LI()
par = [i for i in range(n)]
weight = [0] * n
rank = [0] * n
for _ in range(q):
	lst = LI()
	if lst[0] == 0:
		(x, y, z) = lst[1:4]
		if not same(x, y):
			union(x, y, z)
	else:
		(x, y) = lst[1:3]
		if not same(x, y):
			print('?')
		else:
			print(diff(x, y))


def union(x, y, t):
	(x, t_x) = findSet(x)
	(y, t_y) = findSet(y)
	link(x, t_x, y, t_y, t)

def link(x, d_x, y, d_y, t):
	if r[x] > r[y]:
		p[y] = x
		d[y] = d_x - d_y - t
	else:
		p[x] = y
		d[x] = d_y - d_x + t
		if r[x] == r[y]:
			r[y] = r[y] + 1

def findSet(x):
	if x != p[x]:
		(p[x], t) = findSet(p[x])
		d[x] += t
	return (p[x], d[x])

def diff(x, y):
	(x, d_x) = findSet(x)
	(y, d_y) = findSet(y)
	if x == y:
		return d_x - d_y
	else:
		return '?'
(p, r, d) = ([], [], [])
(n_set, n_quare) = map(int, input().split())
for i in range(n_set):
	p.append(i)
	r.append(0)
	d.append(0)
for i in range(n_quare):
	l = input().split()
	if len(l) == 4:
		(_, x, y, t) = map(int, l)
		union(x, y, t)
	else:
		(_, x, y) = map(int, l)
		print(diff(x, y))


class P_UnionFind:

	def __init__(self, n):
		self.p = [i for i in range(n)]
		self.r = [0 for i in range(n)]
		self.d = [0 for i in range(n)]

	def root(self, x):
		if self.p[x] == x:
			return x
		else:
			tmp = self.root(self.p[x])
			self.d[x] += self.d[self.p[x]]
			self.p[x] = tmp
			return self.p[x]

	def weight(self, x):
		self.root(x)
		return self.d[x]

	def diff(self, x, y):
		if self.root(x) == self.root(y):
			return self.weight(y) - self.weight(x)
		else:
			return '?'

	def uni(self, x, y, w):
		w += self.weight(x)
		w -= self.weight(y)
		x = self.root(x)
		y = self.root(y)
		if x == y:
			return
		if self.r[x] < self.r[y]:
			self.p[x] = y
			self.d[x] = -w
		else:
			self.p[y] = x
			self.d[y] = w
			if self.r[x] == self.r[y]:
				self.r[x] += 1

	def same(self, x, y):
		return self.root(x) == self.root(y)
(n, q) = map(int, input().split())
uf = P_UnionFind(n)
ans = []
for i in range(q):
	d = list(map(int, input().split()))
	if d[0] == 0:
		(c, x, y, z) = d
		uf.uni(x, y, z)
	else:
		(c, x, y) = d
		ans.append(uf.diff(x, y))
print(*ans, sep='\n')


class WeightedUnionFind:

	def __init__(self, n):
		self.par = [i for i in range(n + 1)]
		self.rank = [0] * (n + 1)
		self.weight = [0] * (n + 1)

	def find(self, x):
		if self.par[x] == x:
			return x
		else:
			y = self.find(self.par[x])
			self.weight[x] += self.weight[self.par[x]]
			self.par[x] = y
			return y

	def union(self, x, y, w):
		rx = self.find(x)
		ry = self.find(y)
		if self.rank[rx] < self.rank[ry]:
			self.par[rx] = ry
			self.weight[rx] = w - self.weight[x] + self.weight[y]
		else:
			self.par[ry] = rx
			self.weight[ry] = -w - self.weight[y] + self.weight[x]
			if self.rank[rx] == self.rank[ry]:
				self.rank[rx] += 1

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def diff(self, x, y):
		if self.find(x) != self.find(y):
			return '?'
		return self.weight[x] - self.weight[y]
(n, q) = map(int, input().split())
tree = WeightedUnionFind(n)
for i in range(q):
	query = list(map(int, input().split()))
	if query[0] == 0:
		(x, y, z) = query[1:4]
		tree.union(x, y, z)
	else:
		(x, y) = query[1:3]
		ans = tree.diff(x, y)
		print(ans)


import sys
import time
sys.setrecursionlimit(20000)

def find(tree, x):
	(idx, weight) = tree[x]
	if idx == -1:
		return x
	else:
		root_idx = find(tree, idx)
		tree[x] = (root_idx, tree[x][1] + tree[idx][1])
		return root_idx

def relate(tree, x, y, z):
	x_root_idx = find(tree, x)
	y_root_idx = find(tree, y)
	if x_root_idx == y_root_idx:
		return False
	if tree_rank[y_root_idx] < tree_rank[x_root_idx]:
		tree[y_root_idx] = (x_root_idx, z + tree[x][1] - tree[y][1])
	else:
		tree[x_root_idx] = (y_root_idx, -z - tree[x][1] + tree[y][1])
		if tree_rank[x_root_idx] == tree_rank[y_root_idx]:
			tree_rank[y_root_idx] = tree_rank[y_root_idx] + 1

def diff(tree, x, y):
	x_idx = find(tree, x)
	y_idx = find(tree, y)
	if not x_idx == y_idx:
		return '?'
	return tree[y][1] - tree[x][1]
(N, query_num) = map(int, input().split())
weighted_union_find_tree = [(-1, 0) for _ in range(N)]
tree_rank = [0] * N
for i in range(query_num):
	query = list(map(int, input().split()))
	if query[0] == 0:
		relate(weighted_union_find_tree, query[1], query[2], query[3])
	elif query[0] == 1:
		res = diff(weighted_union_find_tree, query[1], query[2])
		print(res)


class WeightedUnionFindTree:

	def __init__(self, n):
		self.par = list(range(n))
		self.rank = [0] * n
		self.w = [0] * n

	def root(self, x):
		if self.par[x] == x:
			return x
		root = self.root(self.par[x])
		self.w[x] += self.w[self.par[x]]
		self.par[x] = root
		return root

	def relate(self, x, y, weight=0):
		(root_x, root_y) = (self.root(x), self.root(y))
		if root_x == root_y:
			return
		if self.rank[root_x] < self.rank[root_y]:
			self.par[root_x] = root_y
			self.w[root_x] = weight - self.w[x] + self.w[y]
		else:
			self.par[root_y] = root_x
			self.w[root_y] = -weight + self.w[x] - self.w[y]
			if self.rank[root_x] == self.rank[root_y]:
				self.rank[root_x] += 1

	def same(self, x, y):
		return self.root(x) == self.root(y)

	def diff(self, x, y):
		if self.same(x, y):
			return self.w[x] - self.w[y]
		else:
			return '?'

def querying(queries, uft):
	for query in queries:
		if query[0] == 0:
			uft.relate(query[1], query[2], query[3])
		else:
			print(uft.diff(query[1], query[2]))
(n, q) = map(int, input().split())
queries = [list(map(int, input().split())) for i in range(q)]
uft = WeightedUnionFindTree(n)
querying(queries, uft)