Problem:
Problem F:
Never Wait for Weights
In a laboratory, an assistant, Nathan Wada, is measuring weight differences between sample pieces pair by pair. He is using a balance because it can more precisely measure the weight difference between two samples than a spring scale when the samples have nearly the same weight.
He is occasionally asked the weight differences between pairs of samples. He can or cannot answer based on measurement results already obtained.
Since he is accumulating a massive amount of measurement data, it is now not easy for him to promptly tell the weight differences. Nathan asks you to develop a program that records measurement results and automatically tells the weight differences.
Input
The input consists of multiple datasets. The first line of a dataset contains two integers
N
and
M
.
N
denotes the number of sample pieces (2 ≤
N
≤ 100,000). Each sample is assigned a unique number from 1 to
N
as an identifier. The rest of the dataset consists of
M
lines (1 ≤
M
≤ 100,000), each of which corresponds to either a measurement result or an inquiry. They are given in chronological order.
A measurement result has the format,
!
a b w
which represents the sample piece numbered
b
is heavier than one numbered
a
by
w
micrograms (
a
≠
b
). That is,
w
=
w
b
−
w
a
, where
w
a
and
w
b
are the weights of
a
and
b
, respectively. Here,
w
is a non-negative integer not exceeding 1,000,000.
You may assume that all measurements are exact and consistent.
An inquiry has the format,
?
a b
which asks the weight difference between the sample pieces numbered
a
and
b
(
a
≠
b
).
The last dataset is followed by a line consisting of two zeros separated by a space.
Output
For each inquiry, ?
a
b
, print the weight difference in micrograms between the sample pieces numbered
a
and
b
,
w
b
−
w
a
, followed by a newline if the weight difference can be computed based on the measurement results prior to the inquiry. The difference can be zero, or negative as well as positive. You can assume that its absolute value is at most 1,000,000. If the difference cannot be computed based on the measurement results prior to the inquiry, print
UNKNOWN
followed by a newline.
Sample Input
2 2
! 1 2 1
? 1 2
2 2
! 1 2 1
? 2 1
4 7
! 1 2 100
? 2 3
! 2 3 100
? 2 3
? 1 3
! 4 3 150
? 4 1
0 0
Output for the Sample Input
1
-1
UNKNOWN
100
200
-50

Submitted solutions:

code_snippet: #include <iostream>
#include <vector>
#include <utility>
using namespace std;

class unionfind{
public:
    unionfind(int n): n_(n) {
        parent_.resize(n);
        size_.resize(n);
        diff_.resize(n);
        for(int i=0; i<n; i++){
            parent_[i] = i;
            size_[i] = 1;
            diff_[i] = 0;
        }
    }
    // iとjをくっつける その際iから見たjとの差を与える
    void unite(int i, int j, int d){
        int p = root(i);
        int q = root(j);
        if(p==q) return;
        if(size_[p]>size_[q]){
            parent_[q] = p;
            size_[p] += size_[q];
            diff_[q] = diff_[i] + d - diff_[j];
        }else{
            parent_[p] = q;
            size_[q] += size_[p];
            diff_[p] = diff_[j] - d - diff_[i];
        }
    }
    pair<bool,int> diff(int i, int j){
        if(root(i) != root(j)) return make_pair(false, 0);
        return make_pair(true, diff_[j] - diff_[i]);
    }
private:
    int n_;
    vector<int> parent_;
    vector<int> size_;
    vector<int> diff_;  // 親から見たの符号付きの差

    int root(int i){
        if(parent_[i] == i) return i;
        int r = root(parent_[i]);
        diff_[i] = diff_[parent_[i]] + diff_[i];
        parent_[i] = r;
        return r;
    }
};

int main(){
    int n,m;
    while(true){
        cin >> n >> m;
        if((n==0) & (m == 0)){
            break;
        }
        unionfind uf(n+1);
        int x,y,d;
        string s;
        pair<bool,int> ret;
        for(int i=0; i<m; i++){
            cin >> s;
            if(s=="!"){
                cin >> x >> y >> d;
                uf.unite(x,y,d);
            }else{
                cin >> x >> y;
                ret = uf.diff(x,y);
                if(!ret.first){
                    cout << "UNKNOWN" << endl;
                }else{
                    cout << ret.second << endl;
                }                
            }
        }
    }
}
accuracy: 1
time_ms: 970
memory_kb: 4308
final_rank: 16

code_snippet: #include<stdio.h>
#include<string.h>
#include<iostream>
#include<queue>
#include<algorithm>
#define maxn 100010
#define ll long long
using namespace std;
const int MAXSize = 100010;
int uset[MAXSize];
int Rank[MAXSize];
ll w[MAXSize];

void makeSet(int Size)
{
    for(int i = 1; i <= Size; i++)
    {
        uset[i] = i;
        w[i] = 0;
        Rank[i] = 0;
    }
}
int Find(int x)
{
    if (x != uset[x]) return uset[x] = Find(uset[x]);
    return uset[x];
}
void unionSet(int x, int y,int v)
{
    int p = x;
    ll vx = 0;
    while(uset[p] != p){
        vx += w[p];
        p = uset[p];
    }
    int py = y;
    ll vy = 0;
    while(uset[py] != py){
        vy += w[py];
        py = uset[py];
    }
    if(p == py)return;

    if(Rank[p] < Rank[py]){
        uset[p] = py;
        w[p] = vy - v - vx;
    }else{
        uset[py] = p;
        w[py] = v + vx - vy;
        if (Rank[p] == Rank[py]) Rank[p]++;
    }
}

void Get(int x,int y)
{
    int p = x;
    ll vx = 0;
    while(uset[p] != p){
        vx += w[p];
        p = uset[p];
    }
    int py = y;
    ll vy = 0;
    while(uset[py] != py){
        vy += w[py];
        py = uset[py];
    }
    if(p == py){
        printf("%lld\n",vy - vx);
    }else{
        printf("UNKNOWN\n");
    }
}
int main()
{
    int n,m,a,b,c;
    char NOR;
    while(scanf("%d%d",&n,&m)&&n!=0)
    {
        makeSet(n);
        for(int i=0; i<m; i++)
        {
            getchar();
            scanf("%c",&NOR);
            if(NOR=='!')
            {
                scanf("%d%d%d",&a,&b,&c);
                unionSet(a,b,c);
            }
            else
            {
                scanf("%d%d",&a,&b);
                Get(a,b);
            }
        }
    }
    return 0;
}
accuracy: 1
time_ms: 210
memory_kb: 5100
final_rank: 3

code_snippet: #line 1 "1330.test.cpp"
#define PROBLEM "https://onlinejudge.u-aizu.ac.jp/problems/1330"

#line 2 "/home/zawatin/compro/cp-documentation/Src/Template/IOSetting.hpp"

#line 2 "/home/zawatin/compro/cp-documentation/Src/Template/TypeAlias.hpp"

#include <cstdint>
#include <cstddef>

namespace zawa {

using i16 = std::int16_t;
using i32 = std::int32_t;
using i64 = std::int64_t;
using i128 = __int128_t;

using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;

using usize = std::size_t;

} // namespace zawa
#line 4 "/home/zawatin/compro/cp-documentation/Src/Template/IOSetting.hpp"

#include <iostream>
#include <iomanip>

namespace zawa {

void SetFastIO() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
}

void SetPrecision(u32 dig) {
    std::cout << std::fixed << std::setprecision(dig);
}

} // namespace zawa
#line 2 "/home/zawatin/compro/cp-documentation/Src/DataStructure/DisjointSetUnion/PotentializedDisjointSetUnion.hpp"

#line 4 "/home/zawatin/compro/cp-documentation/Src/DataStructure/DisjointSetUnion/PotentializedDisjointSetUnion.hpp"

#include <algorithm>
#include <cassert>
#include <numeric>
#include <vector>

namespace zawa {

template <class Group>
class PotentializedDisjointSetUnion {
public:
    using Value = typename Group::Element;
private:
    usize n_{}, comps_{};
    std::vector<u32> parent_{};
    std::vector<u32> size_{};
    std::vector<Value> potential_{};

    u32 leader(u32 v) {
        if (parent_[v] == v) {
            return v;
        }
        else {
            u32 res{leader(parent_[v])};
            potential_[v] = Group::operation(potential_[parent_[v]], potential_[v]);
            return parent_[v] = res;
        }
    }
    Value potential(u32 v) {
        leader(v);
        return potential_[v];
    }

public:

    PotentializedDisjointSetUnion() = default;

    PotentializedDisjointSetUnion(u32 n) 
        : n_{n}, comps_{n}, parent_(n), size_(n, u32{1}), potential_(n, Group::identity()) {
        std::iota(parent_.begin(), parent_.end(), u32{});
    }

    constexpr u32 size() const noexcept {
        return n_;
    }

    u32 size(u32 v) {
        leader(v);
        return size_[v];
    }

    inline u32 components() const noexcept {
        return comps_;
    }

    bool isDefined(u32 u, u32 v) {
        return leader(u) == leader(v);
    }

    Value distance(u32 u, u32 v) {
        assert(u < size());
        assert(v < size());
        return Group::operation(Group::inverse(potential(u)), potential(v));
    }

    bool merge(u32 u, u32 v, Value value) {
        if (isDefined(u, v)) {
            return distance(u, v) == value;
        }
        comps_--;
        value = Group::operation(potential(u), value);
        value = Group::operation(Group::inverse(potential(v)), value);
        u = leader(u);
        v = leader(v);
        if (size_[u] > size_[v]) {
            value = Group::inverse(value);
            std::swap(u, v);
        }
        size_[u] += size_[v];
        parent_[v] = u;
        potential_[v] = value;
        return true;
    }
};

} // namespace zawa
#line 2 "/home/zawatin/compro/cp-documentation/Src/Algebra/Group/AdditiveGroup.hpp"

namespace zawa {

template <class T>
class AdditiveGroup {
public:
    using Element = T;
    static constexpr T identity() noexcept {
        return T{};
    }
    static constexpr T operation(const T& l, const T& r) noexcept {
        return l + r;
    }
    static constexpr T inverse(const T& v) noexcept {
        return -v;
    }
};

} // namespace zawa
#line 6 "1330.test.cpp"

using namespace zawa;

#line 11 "1330.test.cpp"
int N, Q;
bool solve() {
    std::cin >> N >> Q;
    if (N == 0 and Q == 0) return false;
    PotentializedDisjointSetUnion<AdditiveGroup<int>> dsu(N);
    while (Q--) {
        char t;
        int a, b;
        std::cin >> t >> a >> b;
        a--; b--;
        if (t == '!') {
            int w;
            std::cin >> w;
            dsu.merge(a, b, w);
        }
        else if (t == '?') {
            if (dsu.isDefined(a, b)) std::cout << dsu.distance(a, b) << '\n';
            else std::cout << "UNKNOWN\n";
        }
        else assert(false);
    }
    return true;
}
int main() {
    SetFastIO(); 
    while (solve()) ;
}
accuracy: 1
time_ms: 200
memory_kb: 6304
final_rank: 4

code_snippet: #include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node {
	int v;
	ll dif;
	vector<int> chd;
};
struct UnionFind {
	vector<node> ps;
	UnionFind(int n) : ps(n, {-1, 0, {}}) {
		for (int i = 0; i < n; i++) {
			ps[i].chd.push_back(i);
		}
	}
	int root(int x) {
		if (ps[x].v < 0)
			return x;
		else
			return ps[x].v = root(ps[x].v);
	}
	bool same(int x, int y) { return root(x) == root(y); }
	void unite(int x, int y, int w) {
		int rx = root(x);
		int ry = root(y);
		if (rx == ry) return;
		ll d = ps[x].dif - (w + ps[y].dif);
		if (-ps[rx].v < -ps[ry].v) {
			swap(rx, ry);
			d *= -1;
		}
		for (auto &ty : ps[ry].chd) {
			ps[rx].chd.push_back(ty);
			ps[ty].dif += d;
		}
		ps[ry].chd.clear();
		ps[rx].v += ps[ry].v;
		ps[ry].v = rx;
		return;
	}
};
int main() {
	while (1) {
		int n, m;
		cin >> n >> m;
		if (n == 0 && m == 0) break;
		UnionFind uf(n);
		while (m--) {
			string t;
			cin >> t;
			if (t == "!") {
				int a, b, w;
				cin >> a >> b >> w;
				a--;
				b--;
				uf.unite(a, b, w);
			} else {
				int a, b;
				cin >> a >> b;
				a--;
				b--;
				if (uf.same(a, b)) {
					ll da = uf.ps[a].dif;
					ll db = uf.ps[b].dif;
					cout << da - db << "\n";
				} else {
					cout << "UNKNOWN\n";
				}
			}
		}
	}
	return 0;
}
accuracy: 1
time_ms: 990
memory_kb: 11028
final_rank: 20

code_snippet: #include <bits/stdc++.h>
using namespace std;

// 重み付き Union-Find
template<class Abel> struct UnionFind {
    const Abel UNITY_SUM = 0;      // to be set
    vector<int> par;
    vector<bool> is_nan;
    vector<Abel> diff_weight;

    UnionFind() { }
    UnionFind(int n) : 
    par(n, -1), diff_weight(n, UNITY_SUM) {}
    
    int root(int x) {
        if (par[x] < 0) return x;
        else {
            int r = root(par[x]);
            diff_weight[x] += diff_weight[par[x]];
            return par[x] = r;
        }
    }
    
    Abel calc_weight(int x) {
        root(x);
        return diff_weight[x];
    }
    
    bool issame(int x, int y) {
        return root(x) == root(y);
    }
    
    bool merge(int x, int y, Abel w = 0) {
        w += calc_weight(x); w -= calc_weight(y);
        x = root(x); y = root(y);
        if (x == y) return false;
        if (par[x] > par[y]) swap(x, y), w = -w; // merge technique
        par[x] += par[y];
        par[y] = x;
        diff_weight[y] = w;
        return true;
    }
    
    Abel diff(int x, int y) {
        return calc_weight(y) - calc_weight(x);
    }
    
    int size(int x) {
        return -par[root(x)];
    }
};

int main() {
    // 入力 ("0 0" が来るまで連続で入力を受け取る)
    int N, M;
    while (cin >> N >> M, N) {
        // 重み付き Union-Find を初期化
        UnionFind<int> uf(N);

        for (int i = 0; i < M; ++i) {
            char c;
            cin >> c;
            if (c == '!') {
                int a, b, w;
                cin >> a >> b >> w;
                --a, --b;

                // w[b] - w[a] = w となるように併合
                uf.merge(a, b, w);
            } else {
                int a, b;
                cin >> a >> b;
                --a, --b;

                if (!uf.issame(a, b)) {
                    // a, b が併合されていない場合は w[b] - w[a] の値は未定
                    cout << "UNKNOWN" << endl;
                } else {
                    cout << uf.diff(a, b) << endl;
                }
            }
        }
    }
}
accuracy: 1
time_ms: 900
memory_kb: 3596
final_rank: 10

code_snippet: #include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int INF = 1e9 + 10;
const ll INFL = 4e18;

struct DSU {
    vector<int> par, sz;
    vector<ll> df;

    DSU(int n) {
        par.resize(n);
        iota(par.begin(), par.end(), 0);
        sz.assign(n, 1);
        df.assign(n, 0);
    }

    int find(int x) {
        if (par[x] == x) return x;
        int root = find(par[x]);
        df[x] += df[par[x]];
        return par[x] = root;
    }

    ll weight(int x) {
        find(x);
        return df[x];
    }

    void merge(int x, int y, ll w) {
        w += weight(x);
        w -= weight(y);
        x = find(x), y = find(y);
        if (x == y) return;
        if (sz[x] < sz[y]) swap(x, y), w = -w;
        par[y] = x;
        sz[x] += sz[y];
        df[y] = w;
    }

    ll diff(int x, int y) { return df[y] - df[x]; }

    bool same(int x, int y) { return find(x) == find(y); }
};

int main() {
    while (true) {
        int N, M;
        cin >> N >> M;

        if (N == 0 && M == 0) break;

        DSU dsu(N);
        while (M--) {
            char c;
            cin >> c;

            if (c == '!') {
                int a, b;
                ll w;
                cin >> a >> b >> w;
                a--;
                b--;
                dsu.merge(a, b, w);
            } else {
                int a, b;
                cin >> a >> b;
                a--;
                b--;

                if (!dsu.same(a, b)) {
                    cout << "UNKNOWN\n";
                } else {
                    cout << dsu.diff(a, b) << '\n';
                }
            }
        }
    }
}
accuracy: 1
time_ms: 900
memory_kb: 4708
final_rank: 15

code_snippet: #include<bits/stdc++.h>
using namespace std;

using ll = long long;

struct Unionfind{

	Unionfind(int n){
		par.assign(n, -1);
		pot.assign(n, 0);
	}

	bool merge(int x, int y, ll w){
		w += get_pot(x);
		w -= get_pot(y);
		x = root(x), y = root(y);
		if(x == y){
			if(diff(x, y) == w){
				return true;
			}else{
				return false;
			}
		}
		--c;
		if(par[x] > par[y]){
			swap(x, y);
			w = -w;
		}
		par[x] += par[y];
		par[y] = x;
		pot[y] = w;
		return true;
	}

	bool same(int x, int y){
		return root(x) == root(y);
	}

	int root(int x){
		if(par[x] < 0){
			return x;
		}else {
			int r = root(par[x]);
			pot[x] = pot[x] + pot[par[x]];
			return par[x] = r;
		}
	}

	ll get_pot(int x){
		root(x);
		return pot[x];
	}

	ll diff(int x, int y){
		return get_pot(x) - get_pot(y);
	}

	int n, c;
	vector<int> par;
	vector<ll> pot;
};

int main(){
	int n, q;
	while(1){
		cin >> n >> q;
		if(n == 0 && q == 0) break;
		Unionfind uf(n);
		while(q--){
			char c;
			cin >> c;
			if(c == '!'){
				int x, y;
				ll w;
				cin >> x >> y >> w;
				--x, --y;
				uf.merge(x, y, w);
			}else{
				int x, y;
				cin >> x >> y;
				--x, --y;
				if(uf.same(x, y)){
					cout << uf.diff(y, x) << endl;
				}else{
					cout << "UNKNOWN" << endl;
				}
			}
		}
	}
	return 0;
}
accuracy: 1
time_ms: 620
memory_kb: 4700
final_rank: 7

code_snippet: #include <iostream>
#include <algorithm>
#include <cassert>
#include <numeric>
#include <vector>
struct DSU {
    int n;
    std::vector<int> par, size, po;
    DSU(int n_) : n{n_}, par(n_), size(n_, 1), po(n_, 0) {
        std::iota(par.begin(), par.end(), 0);
    }
    int leader(int v) {
        // for (auto p : par) std::cout << p << ' ';
        // std::cout << std::endl;
        // std::cout << v << " leader" << std::endl;
        assert(0 <= v and v < n);
        if (par[v] == v) return v;
        else {
            int res{leader(par[v])};
            po[v] = po[par[v]] + po[v];
            return par[v] = res;
        }
    }
    int potential(int v) {
        leader(v);
        return po[v];
    }
    bool same(int u, int v) {
        // std::cout << "same " << u << ' ' << v << std::endl;
        return leader(u) == leader(v);
    }
    int distance(int u, int v) {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        // std::cout << u << ' ' << v << ' ' << "distance" << std::endl;
        return -po.at(u) + po.at(v);
    }
    bool merge(int u, int v, int val) {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        if (same(u, v)) return distance(u, v) == val;
        val = po.at(u) + val;
        val = -po.at(v) + val; 
        u = leader(u);
        v = leader(v);
        if (size[u] > size[v]) {
            val = -val;
            std::swap(u, v);
        }
        size[u] += size[v];
        par[v] = u;
        po[v] = val;
        // std::cout << u << ' ' << v << ' ' << val << "!" << std::endl;
        return true;
    }
};
int N, M;
void solve() {
    // std::cout << N << ' ' << M << " TEST START" << std::endl;
    DSU dsu(N);
    while (M--) {
        char c;
        std::cin >> c;
        if (c == '!') {
            int a, b, w;
            std::cin >> a >> b >> w;
            a--; b--;
            assert(dsu.merge(a, b, w));
            // std::cout << "merge end" << std::endl;
        }
        else if (c == '?') {
            int a, b;
            std::cin >> a >> b;
            a--; b--;
            // std::cout << "try to calc " << a << ' ' << b << std::endl;
            if (dsu.same(a, b)) std::cout << dsu.distance(a, b) << std::endl;
            else std::cout << "UNKNOWN" << std::endl;;
        }
        else {
            assert(false);
        }
    }
    // std::cout << "TEST END" << std::endl;
}
int main() {
    while (true) {
        std::cin >> N >> M;
        if (N == 0 and M == 0) break;
        solve();
    }
}
accuracy: 1
time_ms: 910
memory_kb: 6256
final_rank: 18

code_snippet: #include <bits/stdc++.h>

using namespace std;

#define rep(i, a, b) for (int i = (int)(a); i < (int)(b); i++)
#define rrep(i, a, b) for (int i = (int)(b)-1; i >= (int)(a); i--)
#define ALL(v) (v).begin(), (v).end()
#define UNIQUE(v) sort(ALL(v)), (v).erase(unique(ALL(v)), (v).end())
#define SZ(v) (int)v.size()
#define MIN(v) *min_element(ALL(v))
#define MAX(v) *max_element(ALL(v))
#define LB(v, x) int(lower_bound(ALL(v), (x)) - (v).begin())
#define UB(v, x) int(upper_bound(ALL(v), (x)) - (v).begin())

using uint = unsigned int;
using ll = long long int;
using ull = unsigned long long;
using i128 = __int128_t;
using u128 = __uint128_t;
const int inf = 0x3fffffff;
const ll INF = 0x1fffffffffffffff;

template <typename T> inline bool chmax(T &a, T b) {
    if (a < b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T> inline bool chmin(T &a, T b) {
    if (a > b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T, typename U> T ceil(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? (x + y - 1) / y : x / y);
}
template <typename T, typename U> T floor(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? x / y : (x - y + 1) / y);
}
template <typename T> int popcnt(T x) {
    return __builtin_popcountll(x);
}
template <typename T> int topbit(T x) {
    return (x == 0 ? -1 : 63 - __builtin_clzll(x));
}
template <typename T> int lowbit(T x) {
    return (x == 0 ? -1 : __builtin_ctzll(x));
}

template <class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
    os << "P(" << p.first << ", " << p.second << ")";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const vector<T> &vec) {
    os << "{";
    for (int i = 0; i < vec.size(); i++) {
        os << vec[i] << (i + 1 == vec.size() ? "" : ", ");
    }
    os << "}";
    return os;
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &map_var) {
    os << "{";
    for (auto itr = map_var.begin(); itr != map_var.end(); itr++) {
        os << "(" << itr->first << ", " << itr->second << ")";
        itr++;
        if (itr != map_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const set<T> &set_var) {
    os << "{";
    for (auto itr = set_var.begin(); itr != set_var.end(); itr++) {
        os << *itr;
        ++itr;
        if (itr != set_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
#ifdef LOCAL
#define show(...) _show(0, #__VA_ARGS__, __VA_ARGS__)
#else
#define show(...) true
#endif
template <typename T> void _show(int i, T name) {
    cerr << '\n';
}
template <typename T1, typename T2, typename... T3>
void _show(int i, const T1 &a, const T2 &b, const T3 &...c) {
    for (; a[i] != ',' && a[i] != '\0'; i++)
        cerr << a[i];
    cerr << ":" << b << " ";
    _show(i + 1, a, c...);
}

#include <unistd.h>

namespace fastio {
static constexpr uint32_t SZ = 1 << 17;
char ibuf[SZ];
char obuf[SZ];
char out[100];
// pointer of ibuf, obuf


uint32_t pil = 0, pir = 0, por = 0;

struct Pre {
    char num[10000][4];
    constexpr Pre() : num() {
        for (int i = 0; i < 10000; i++) {
            int n = i;
            for (int j = 3; j >= 0; j--) {
                num[i][j] = n % 10 | '0';
                n /= 10;
            }
        }
    }
} constexpr pre;

inline void load() {
    memmove(ibuf, ibuf + pil, pir - pil);
    pir = pir - pil + fread(ibuf + pir - pil, 1, SZ - pir + pil, stdin);
    pil = 0;
    if (pir < SZ)
        ibuf[pir++] = '\n';
}

inline void flush() {
    fwrite(obuf, 1, por, stdout);
    por = 0;
}

void rd(char &c) {
    do {
        if (pil + 1 > pir)
            load();
        c = ibuf[pil++];
    } while (isspace(c));
}

void rd(string &x) {
    x.clear();
    char c;
    do {
        if (pil + 1 > pir)
            load();
        c = ibuf[pil++];
    } while (isspace(c));
    do {
        x += c;
        if (pil == pir)
            load();
        c = ibuf[pil++];
    } while (!isspace(c));
}

template <typename T> void rd_real(T &x) {
    string s;
    rd(s);
    x = stod(s);
}

template <typename T> void rd_integer(T &x) {
    if (pil + 100 > pir)
        load();
    char c;
    do
        c = ibuf[pil++];
    while (c < '-');
    bool minus = 0;
    if constexpr (is_signed<T>::value || is_same_v<T, i128>) {
        if (c == '-') {
            minus = 1, c = ibuf[pil++];
        }
    }
    x = 0;
    while ('0' <= c) {
        x = x * 10 + (c & 15), c = ibuf[pil++];
    }
    if constexpr (is_signed<T>::value || is_same_v<T, i128>) {
        if (minus)
            x = -x;
    }
}

void rd(int &x) {
    rd_integer(x);
}
void rd(ll &x) {
    rd_integer(x);
}
void rd(i128 &x) {
    rd_integer(x);
}
void rd(uint &x) {
    rd_integer(x);
}
void rd(ull &x) {
    rd_integer(x);
}
void rd(u128 &x) {
    rd_integer(x);
}
void rd(double &x) {
    rd_real(x);
}
void rd(long double &x) {
    rd_real(x);
}

template <class T, class U> void rd(pair<T, U> &p) {
    return rd(p.first), rd(p.second);
}
template <size_t N = 0, typename T> void rd_tuple(T &t) {
    if constexpr (N < std::tuple_size<T>::value) {
        auto &x = std::get<N>(t);
        rd(x);
        rd_tuple<N + 1>(t);
    }
}
template <class... T> void rd(tuple<T...> &tpl) {
    rd_tuple(tpl);
}

template <size_t N = 0, typename T> void rd(array<T, N> &x) {
    for (auto &d : x)
        rd(d);
}
template <class T> void rd(vector<T> &x) {
    for (auto &d : x)
        rd(d);
}

void read() {}
template <class H, class... T> void read(H &h, T &...t) {
    rd(h), read(t...);
}

void wt(const char c) {
    if (por == SZ)
        flush();
    obuf[por++] = c;
}
void wt(const string s) {
    for (char c : s)
        wt(c);
}
void wt(const char *s) {
    size_t len = strlen(s);
    for (size_t i = 0; i < len; i++)
        wt(s[i]);
}

template <typename T> void wt_integer(T x) {
    if (por > SZ - 100)
        flush();
    if (x < 0) {
        obuf[por++] = '-', x = -x;
    }
    int outi;
    for (outi = 96; x >= 10000; outi -= 4) {
        memcpy(out + outi, pre.num[x % 10000], 4);
        x /= 10000;
    }
    if (x >= 1000) {
        memcpy(obuf + por, pre.num[x], 4);
        por += 4;
    } else if (x >= 100) {
        memcpy(obuf + por, pre.num[x] + 1, 3);
        por += 3;
    } else if (x >= 10) {
        int q = (x * 103) >> 10;
        obuf[por] = q | '0';
        obuf[por + 1] = (x - q * 10) | '0';
        por += 2;
    } else
        obuf[por++] = x | '0';
    memcpy(obuf + por, out + outi + 4, 96 - outi);
    por += 96 - outi;
}

template <typename T> void wt_real(T x) {
    ostringstream oss;
    oss << fixed << setprecision(15) << double(x);
    string s = oss.str();
    wt(s);
}

void wt(int x) {
    wt_integer(x);
}
void wt(ll x) {
    wt_integer(x);
}
void wt(i128 x) {
    wt_integer(x);
}
void wt(uint x) {
    wt_integer(x);
}
void wt(ull x) {
    wt_integer(x);
}
void wt(u128 x) {
    wt_integer(x);
}
void wt(double x) {
    wt_real(x);
}
void wt(long double x) {
    wt_real(x);
}

template <class T, class U> void wt(const pair<T, U> val) {
    wt(val.first);
    wt(' ');
    wt(val.second);
}
template <size_t N = 0, typename T> void wt_tuple(const T t) {
    if constexpr (N < std::tuple_size<T>::value) {
        if constexpr (N > 0) {
            wt(' ');
        }
        const auto x = std::get<N>(t);
        wt(x);
        wt_tuple<N + 1>(t);
    }
}
template <class... T> void wt(tuple<T...> tpl) {
    wt_tuple(tpl);
}
template <class T, size_t S> void wt(const array<T, S> val) {
    auto n = val.size();
    for (size_t i = 0; i < n; i++) {
        if (i)
            wt(' ');
        wt(val[i]);
    }
}
template <class T> void wt(const vector<T> val) {
    auto n = val.size();
    for (size_t i = 0; i < n; i++) {
        if (i)
            wt(' ');
        wt(val[i]);
    }
}

void print() {
    wt('\n');
}
template <class Head, class... Tail> void print(Head &&head, Tail &&...tail) {
    wt(head);
    if (sizeof...(Tail))
        wt(' ');
    print(forward<Tail>(tail)...);
}
void __attribute__((destructor)) _d() {
    flush();
}
} // namespace fastio


using fastio::flush;
using fastio::print;
using fastio::read;

inline void first(bool i = true) {
    print(i ? "first" : "second");
}
inline void Alice(bool i = true) {
    print(i ? "Alice" : "Bob");
}
inline void Takahashi(bool i = true) {
    print(i ? "Takahashi" : "Aoki");
}
inline void yes(bool i = true) {
    print(i ? "yes" : "no");
}
inline void Yes(bool i = true) {
    print(i ? "Yes" : "No");
}
inline void No() {
    print("No");
}
inline void YES(bool i = true) {
    print(i ? "YES" : "NO");
}
inline void NO() {
    print("NO");
}
inline void Yay(bool i = true) {
    print(i ? "Yay!" : ":(");
}
inline void Possible(bool i = true) {
    print(i ? "Possible" : "Impossible");
}
inline void POSSIBLE(bool i = true) {
    print(i ? "POSSIBLE" : "IMPOSSIBLE");
}

/**
 * @brief Fast IO
 */

struct dsu {
  public:
    dsu() : _n(0) {}
    dsu(int n) : _n(n), parent_or_size(n, -1), weight(n,0) {}

    int merge(int a, int b, ll w) {
        assert(0 <= a && a < _n);
        assert(0 <= b && b < _n);
        w += calc_weight(a), w -= calc_weight(b);
        int x = leader(a), y = leader(b);
        if (x == y) return x;
        if (-parent_or_size[x] < -parent_or_size[y]) std::swap(x, y), w = -w;
        parent_or_size[x] += parent_or_size[y];
        parent_or_size[y] = x;
        weight[y] = w;
        return x;
    }

    bool same(int a, int b) {
        assert(0 <= a && a < _n);
        assert(0 <= b && b < _n);
        return leader(a) == leader(b);
    }

    int leader(int a) {
        assert(0 <= a && a < _n);
        if (parent_or_size[a] < 0) return a;
        int l = leader(parent_or_size[a]);
        weight[a] += weight[parent_or_size[a]];
        return parent_or_size[a] = l;
    }

    ll calc_weight(int a) {
        leader(a);
        return weight[a];
    }

    ll diff(int a, int b) {
        return calc_weight(b) - calc_weight(a);
    }

  private:
    int _n;
    // root node: -1 * component size
    // otherwise: parent
    std::vector<int> parent_or_size;
    vector<ll> weight;
};

int main() {
while(1) {
    int N, M;
    cin >> N >> M;
    if (N == 0 && M == 0) return 0;
    dsu UF(N);
    rep(i,0,M) {
        char C;
        cin >> C;
        if (C == '!') {
            int A, B;
            ll W;
            cin >> A >> B >> W;
            A--, B--;
            UF.merge(A,B,W);
        }
        else {
            int A, B;
            cin >> A >> B;
            A--, B--;
            if (!UF.same(A,B)) {
                cout << "UNKNOWN" << endl;
            }
            else {
                cout << UF.diff(A,B) << endl;
            }
        }
    }
}
}
accuracy: 1
time_ms: 900
memory_kb: 4576
final_rank: 13

code_snippet: #if 1
#include<cmath>
#include<limits>
#include<iostream>
#include<algorithm>
#include<functional>
#include<string>
#include<map>
#include<set>
#include<ctime>
#include<queue>
#include<vector>
#include<tuple>
// #include<format>
#ifdef _INTEGRAL_MAX_BITS
// #include"C:\Users\Spare\source\repos\Competitive_programming\Competitive_programming\debugging.h"
#endif
#ifndef DEBUGGING
#define MY_MAIN int main()
#define REDIRCT_IO ;
#define RESTORE_IO ;
#endif
#define sc static
#define ct const
#define ft first
#define sd second
using namespace std;
// using namespace atcoder;
// using namespace Eigen;
using ld = long double;
using ll = long long;
using cchar = const char;
using cint = const int;
using cll = const ll;
using uchar = unsigned char;
using uint = unsigned int;
using ull = unsigned long long;
using cuchar = const uchar;
using cuint = const uint;
using cull = const ull;
template<class TYPE> static const TYPE INF = numeric_limits<TYPE>::max();
#define rep(i, n) for(int i = (0), fend = n; i < fend; ++i)
static const string ascii_lowercase = "abcdefghijklmnopqrstuvwxyz";
static const string ascii_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
template<class Abel> struct UnionFind {
	vector<int> par;
	vector<int> rank;
	vector<Abel> diff_weight;

	UnionFind(int n = 1, Abel SUM_UNITY = 0) {
		init(n, SUM_UNITY);
	}

	void init(int n = 1, Abel SUM_UNITY = 0) {
		par.resize(n); rank.resize(n); diff_weight.resize(n);
		for (int i = 0; i < n; ++i) par[i] = i, rank[i] = 0, diff_weight[i] = SUM_UNITY;
	}

	int root(int x) {
		if (par[x] == x) {
			return x;
		}
		else {
			int r = root(par[x]);
			diff_weight[x] += diff_weight[par[x]];
			return par[x] = r;
		}
	}

	Abel weight(int x) {
		root(x);
		return diff_weight[x];
	}

	bool issame(int x, int y) {
		return root(x) == root(y);
	}

	bool merge(int x, int y, Abel w) {
		w += weight(x); w -= weight(y);
		x = root(x); y = root(y);
		if (x == y) return false;
		if (rank[x] < rank[y]) swap(x, y), w = -w;
		if (rank[x] == rank[y]) ++rank[x];
		par[y] = x;
		diff_weight[y] = w;
		return true;
	}

	Abel diff(int x, int y) {
		return weight(y) - weight(x);
	}
};
int N, M;
int main() {
    REDIRCT_IO;
	while (true) {
		cin >> N >> M;
		if (N == 0)break;
		UnionFind<ll> uf(N);
		char c; int a, b; ll w;
		rep(t, M) {
			cin >> c;
			if (c == '!') {
				cin >> a >> b >> w; --a; --b;
				uf.merge(a, b, w);
			}
			else {
				cin >> a >> b; --a; --b;
				if (uf.issame(a, b)) {
					cout << uf.diff(a, b) << '\n';
				}
				else {
					cout << "UNKNOWN" << endl;
				}
			}
		}
		cout << flush;
	}
    RESTORE_IO;
    return 0;
}

#endif
accuracy: 1
time_ms: 900
memory_kb: 4704
final_rank: 14

code_snippet: #line 2 "cp-library/src/cp-template.hpp"
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using uint = unsigned int;
using ull  = unsigned long long;
using i32 = int;
using u32 = unsigned int;
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128_t;
template < class T > bool chmin(T& a, T b) { if(a > b) { a = b; return true; } return false; }
template < class T > bool chmax(T& a, T b) { if(a < b) { a = b; return true; } return false; }
template < class T, class U > T ceil (T x, U y) { return (x > 0 ? (x + y - 1) / y :           x / y); }
template < class T, class U > T floor(T x, U y) { return (x > 0 ?           x / y : (x - y + 1) / y); }
int popcnt(i32 x) { return __builtin_popcount(x); }
int popcnt(u32 x) { return __builtin_popcount(x); }
int popcnt(i64 x) { return __builtin_popcountll(x); }
int popcnt(u64 x) { return __builtin_popcountll(x); }

#line 2 "cp-library/src/utility/rep_itr.hpp"
template < class T > struct itr_rep {
    T i, d;
    constexpr itr_rep(const T i) noexcept : i(i), d(1) {}
    constexpr itr_rep(const T i, const T d) noexcept : i(i), d(d) {}
    void operator++() noexcept { i += d; }
    constexpr int operator*() const noexcept { return i; }
    constexpr bool operator!=(const itr_rep x) const noexcept { return d > 0 ? i < x.i : i > x.i; }
};

template < class T > struct rep {
    const itr_rep< T > s, t;
    constexpr rep(const T t) noexcept : s(0), t(t) {}
    constexpr rep(const T s, const T t) noexcept : s(s), t(t) {}
    constexpr rep(const T s, const T t, const T d) noexcept : s(s, d), t(t, d) {}
    constexpr auto begin() const noexcept { return s; }
    constexpr auto end  () const noexcept { return t; }
};

template < class T > struct revrep {
    const itr_rep < T > s, t;
    constexpr revrep(const T t) noexcept : s(t - 1, -1), t(-1, -1) {}
    constexpr revrep(const T s, const T t) noexcept : s(t - 1, -1), t(s - 1, -1) {}
    constexpr revrep(const T s, const T t, const T d) noexcept : s(t - 1, -d), t(s - 1, -d) {}
    constexpr auto begin() const noexcept { return s; }
    constexpr auto end  () const noexcept { return t; }
};
#line 3 "cp-library/src/utility/io.hpp"

/* 128bit integer */
istream& operator>>(istream& is, i128& x) {
    std::string s; is >> s;
    int pm = (s[0] == '-');
    x = 0;
    for(int i : rep(pm, int(s.size()))) x = x * 10 + (s[i] - '0');
    if(pm) x *= -1;
    return is;
}
ostream& operator<<(ostream& os, const i128& x) {
    if(x == 0) return os << '0';
    i128 y = x;
    if(y < 0) { os << '-'; y *= -1; }
    std::vector<int> ny;
    while(y > 0) { ny.push_back(y % 10); y /= 10; }
    for(int i : revrep(ny.size())) os << ny[i];
    return os;
}

template < class S, class T > istream& operator>>(istream& is,       std::pair< S, T >& x) { is >> x.first        >> x.second; return is; }
template < class S, class T > ostream& operator<<(ostream& os, const std::pair< S, T >& x) { os << x.first << " " << x.second; return os; }

namespace scanner {
    struct sca {
        template < class T > operator T() {
            T s; std::cin >> s; return s;
        }
    };
    struct vec {
        int n;
        vec(int n) : n(n) {}
        template < class T > operator std::vector< T >() {
            std::vector< T > v(n);
            for(T& x : v) std::cin >> x;
            return v;
        }
    };
    struct mat {
        int h, w;
        mat(int h, int w) : h(h), w(w) {}
        template < class T > operator std::vector< std::vector< T > >() {
            std::vector m(h, std::vector< T >(w));
            for(std::vector< T >& v : m) for(T& x : v) std::cin >> x;
            return m;
        }
    };
    struct speedup {
        speedup() {
            std::cin.tie(0);
            std::ios::sync_with_stdio(0);
        }
    } speedup_instance;
}
scanner::sca in() { return scanner::sca(); }
scanner::vec in(int n) { return scanner::vec(n); }
scanner::mat in(int h, int w) { return scanner::mat(h, w); }

namespace printer {
    void precision(int d) { std::cout << std::fixed << std::setprecision(d); }
    void flush() { std::cout.flush(); }
}

template < class T >
ostream& operator<<(ostream& os, const std::vector< T > a) {
    int n = a.size();
    for(int i : rep(n)) { os << a[i]; if(i != n - 1) os << ' '; }
    return os;
}

int print() { std::cout << '\n'; return 0; }
template < class head, class... tail > int print(head&& h, tail&&... t) {
    std::cout << h; if(sizeof...(tail)) std::cout << ' ';
    return print(std::forward<tail>(t)...);
}
template < class T > int print_n(const std::vector< T > a) {
    int n = a.size();
    for(int i : rep(n)) std::cout << a[i] << "\n";
    return 0;
}


#line 2 "cp-library/src/utility/key_val.hpp"

template < class K, class V >
struct key_val {
    K key; V val;
    key_val() {}
    key_val(K key, V val) : key(key), val(val) {}
    template < std::size_t Index >
    std::tuple_element_t< Index, key_val >& get() {
        if constexpr (Index == 0) return key;
        if constexpr (Index == 1) return val;
    }
};

namespace std {

template < class K, class V > struct tuple_size < key_val< K, V > > : integral_constant< size_t, 2 > {};
template < class K, class V > struct tuple_element < 0, key_val< K, V > > { using type = K; };
template < class K, class V > struct tuple_element < 1, key_val< K, V > > { using type = V; };

}
#line 2 "cp-library/src/utility/vec_op.hpp"
template < class T > key_val< int, T > max_of(const vector< T >& a) {
    int i = std::max_element(a.begin(), a.end()) - a.begin();
    return {i, a[i]};
}
template < class T > key_val< int, T > min_of(const vector< T >& a) {
    int i = std::min_element(a.begin(), a.end()) - a.begin();
    return {i, a[i]};
}
template < class S, class T > S sum_of(const vector< T >& a) {
    S sum = 0;
    for(const T x : a) sum += x;
    return sum;
}
template < class S, class T > vector< S > freq_of(const vector< T >& a, T L, T R) {
    vector< S > res(R - L, S(0));
    for(const T x : a) res[x - L] += 1;
    return res;
}
template < class S, class T > struct prefix_sum {
    vector< S > s;
    prefix_sum(const vector< T >& a) : s(a) {
        s.insert(s.begin(), S(0));
        for(int i : rep(a.size())) s[i + 1] += s[i];
    }
    // [L, R)
    S sum(int L, int R) { return s[R] - s[L]; }
};
#line 3 "cp-library/src/utility/heap.hpp"

template < class T > using heap_min = std::priority_queue< T, std::vector< T >, std::greater< T > >;
template < class T > using heap_max = std::priority_queue< T, std::vector< T >, std::less< T > >;

#line 27 "cp-library/src/cp-template.hpp"

#line 1 "cp-library/src/algorithm/bin_search.hpp"
template < class T, class F >
T bin_search(T ok, T ng, F f) {
    while(abs(ng - ok) > 1) {
        T mid = (ok + ng) / 2;
        (f(mid) ? ok : ng) = mid;
    }
    return ok;
}

template < class T, class F >
T bin_search_real(T ok, T ng, F f, int step = 80) {
    while(step--) {
        T mid = (ok + ng) / 2;
        (f(mid) ? ok : ng) = mid;
    }
    return ok;
}
#line 2 "cp-library/src/algorithm/argsort.hpp"

template < class T > std::vector< int > argsort(const std::vector< T > &a) {
    std::vector< int > ids((int)a.size());
    std::iota(ids.begin(), ids.end(), 0);
    std::sort(ids.begin(), ids.end(), [&](int i, int j) {
        return a[i] < a[j] || (a[i] == a[j] && i < j);
    });
    return ids;
}
#line 1 "macro.hpp"
namespace macro {

using size_type = int;
template < class container > void  sort(container& a) { std::sort(std:: begin(a), std:: end(a)); }
template < class container > void rsort(container& a) { std::sort(std::rbegin(a), std::rend(a)); }
template < class container > void reverse(container& a) { std::reverse(std::begin(a), std::end(a)); }
template < class container > void unique(container& a) {
    std::sort(std::begin(a), std::end(a));
    a.erase(std::unique(std::begin(a), std::end(a)), std::end(a));
}
template < class container > container  sorted(const container& a) { container b = a;  sort(b); return std::move(b); }
template < class container > container rsorted(const container& a) { container b = a; rsort(b); return std::move(b); }
template < class container, class compare > void sort(container& a, const compare& cmp) { std::sort(std::begin(a), std::end(a), cmp); }
template < class container, class compare > container sorted(const container& a, const compare& cmp) { container b = a; sort(b, cmp); return std::move(b); }
template < class container, class value > size_type lower_bound(const container& a, const value& x) { return std::lower_bound(std::begin(a), std::end(a), x) - std::begin(a); }
template < class container, class value > size_type upper_bound(const container& a, const value& x) { return std::upper_bound(std::begin(a), std::end(a), x) - std::begin(a); }

const std::vector<std::pair<size_type, size_type>> dir4 = { {+1,  0}, {-1,  0}, { 0, +1}, { 0, -1} };
const std::vector<std::pair<size_type, size_type>> dir8 = { {-1, -1}, {-1,  0}, {-1, +1}, { 0, -1}, { 0, +1}, {+1, -1}, {+1,  0}, {+1, +1} };

#ifdef _DEBUG
#define debug(x) std::cout << "[" << __LINE__ << "] " << #x << ": " << x << std::endl
#else
#define debug(x)
#endif

template < class container > void concat(container& a, const container& b) {
    a.insert(std::end(a), std::begin(b), std::end(b));
}
std::vector<size_type> iota(const size_type n) {
    std::vector<size_type> I(n);
    std::iota(std::begin(I), std::end(I), 0);
    return I;
}
template < class container > std::vector<size_type> sort_idx(const container& a) {
    const size_type n = a.size();
    std::vector<size_type> I = iota(n);
    std::sort(std::begin(I), std::end(I), [&](size_type i, size_type j) { return a[i] < a[j] or (a[i] == a[j] and i < j); });
    return I;
}
template < class container, class compare > std::vector<size_type> sort_idx(const container& a, const compare& cmp) {
    const size_type n = a.size();
    std::vector<size_type> I = iota(n);
    std::sort(std::begin(I), std::end(I), [&](size_type i, size_type j) { return cmp(a[i], a[j]) or (a[i] == a[j] and i < j); });
    return std::move(I);
}

struct grid {
    using size_type = int;
    size_type H, W;
    grid(const size_type H, const size_type W) : H(H), W(W) {}
    bool contains(const size_type i, const size_type j) {
        return 0 <= i and i < H and 0 <= j and j < W;
    }
};

} // namespace macro

using namespace macro;
#line 1 "cp-library/src/algebra/sum.hpp"
template < class T > class sum_monoid {
  public:
    using set = T;
    static constexpr T op(const T &l, const T &r) { return l + r; }
    static constexpr T id() { return T(0); }
    static constexpr T inv(const T &x) { return -x; }
    static constexpr T pow(const T &x, const ll n) { return x * n; }
    static constexpr bool comm = true;
};
#line 4 "A.cpp"

template < class Group > class potentialized_union_find {
  public:
    using T = typename Group::set;

  private:
    using size_t = std::size_t;
    class node_type {
        friend potentialized_union_find;
        T value;
        size_t parent;
        size_t size;

        node_type(const T value, const size_t parent, const size_t size)
            : value(value), parent(parent), size(size) {}
    };

    std::vector<node_type> tree;
    size_t size() const { return tree.size(); }
    void compress(const size_t x) {
        const std::size_t p = tree[x].parent;
        if(p == x) return;
        compress(p);
        tree[x].value = Group::op(tree[p].value, tree[x].value);
        tree[x].parent = tree[p].parent;
    }

  public:
    potentialized_union_find() = default;
    explicit potentialized_union_find(const size_t n)
        : tree(n, node_type(Group::id(), 0, 1)) {
        for(size_t i = 0; i < n; i++) tree[i].parent = i;
    }

    size_t find(const size_t x) {
        assert(x < size());
        compress(x);
        return tree[x].parent;
    }
    T potential(const size_t x) {
        assert(x < size());
        compress(x);
        return tree[x].value;
    }
    bool same(const size_t x, const size_t y) {
        assert(x < size());
        compress(x);
        return find(x) == find(y);
    }
    T distance(const size_t x, const size_t y) {
        assert(x < size());
        assert(y < size());
        return Group::op(Group::inv(potential(x)), potential(y));
    }
    size_t size(const size_t x) {
        assert(x < size());
        return tree[find(x)].size;
    }
    void unite(size_t x, size_t y, T d) {
        if(same(x, y)) return;
        if(size(x) < size(y)) {
            d = Group::inv(d);
            std::swap(x, y);
        }
        d = Group::op(Group::op(potential(x), d), Group::inv(potential(y)));
        x = find(x), y = find(y);
        tree[x].size += tree[y].size;
        tree[y].parent = x;
        tree[y].value = d;
    }
};

void solve(int N, int M) {
    potentialized_union_find<sum_monoid<int>> uf(N);
    for(int M_ : rep(M)) {
        char type = in();
        if(type == '!') {
            int a = in(), b = in(), w = in(); a--, b--;
            uf.unite(a, b, w);
        } else {
            int a = in(), b = in(); a--, b--;
            if(uf.same(a, b)) {
                print(uf.distance(a, b));
            } else {
                print("UNKNOWN");
            }
            printer::flush();
        }
    }
}

int main() {
    while(true) {
        int N = in(), M = in();
        if(make_pair(N, M) == make_pair(0, 0)) return 0;
        solve(N, M);
    }
}
accuracy: 1
time_ms: 560
memory_kb: 5816
final_rank: 8

code_snippet: #define _CRT_SECURE_NO_WARNINGS
#pragma GCC target "no-avx"

#if USE_MP
#include <boost/multiprecision/cpp_int.hpp>
#endif
#include <cassert>
#include <cstdio>
#include <iostream>
#include <iomanip>
#include <string>
#include <array>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <queue>
#include <stack>
#include <functional>
#include <algorithm>
#include <cmath>
#include <tuple>
#include <bitset>
#include <string.h>
#include <numeric>
#include <random>
#if defined _DEBUG
//#include "TestCase.h"
//#include "Util.h"
#endif

using namespace std;

using ll = long long;
using ull = unsigned long long;
using ld = long double;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<ll, ll, ll> tlll;
typedef tuple<int, int, int> tiii;

using ai2 = array<int, 2>;
using ai3 = array<int, 3>;
using ai4 = array<int, 4>;
using al2 = array<ll, 2>;
using al3 = array<ll, 3>;
using al4 = array<ll, 4>;
using ad2 = array<ld, 2>;
using ad3 = array<ld, 3>;
using ad4 = array<ld, 4>;
#define REP(i, n)    for (int i = 0; i < (n); i++)
inline void Yes(bool upper = false) { cout << (upper ? "YES" : "Yes") << "\n"; }
inline void No(bool upper = false) { cout << (upper ? "NO" : "No") << "\n"; }

inline ll DivCeil(ll nume, ll deno)
{
	assert(deno != 0);
	if (deno < 0) { nume = -nume; deno = -deno; }
	if (nume < 0) return -(-nume / deno);
	else return (nume + deno - 1) / deno;
}
inline ll DivFloor(ll nume, ll deno)
{
	assert(deno != 0);
	if (deno < 0) { nume = -nume; deno = -deno; }
	if (nume < 0) return -((-nume + deno - 1) / deno);
	else return nume / deno;
}
inline ll DivRound(ll nume, ll deno)
{
	assert(deno != 0);
	if (deno < 0) { nume = -nume; deno = -deno; }
	if (nume < 0) return -((-nume + deno / 2) / deno);
	else return (nume + deno / 2) / deno;
}

template <typename T, int S>
auto MakeVecImpl(queue<int>& size, const T& ini)
{
	if constexpr (S == 1)
	{
		return vector<T>(size.front(), ini);
	}
	else
	{
		int fsize = size.front();
		size.pop();
		return vector(fsize, MakeVecImpl<T, S - 1>(size, ini));
	}
}

template <typename T, int S>
auto MakeVec(const array<int, S>& size, const T ini = T())
{
	queue<int> qsize;
	for (const auto v : size) qsize.push(v);
	return MakeVecImpl<T, S>(qsize, ini);
}
int d4[][2] =
{
	{0,-1},
	{0,1},
	{-1,0},
	{1,0},
};
int d8[][2] =
{
	{-1,-1},
	{0,-1},
	{1,-1},

	{-1,0},
	{1,0},

	{-1,1},
	{0,1},
	{1,1},
};
vector<array<int, 2>> Neighbor2(int y, int x)
{
	vector<array<int, 2>> ret;
	ret.push_back({ y, x + 1 });
	ret.push_back({ y + 1, x });
	return ret;
}
vector<array<int, 2>> Neighbor4(int y, int x)
{
	vector<array<int, 2>> ret;
	ret.push_back({ y, x - 1 });
	ret.push_back({ y, x + 1 });
	ret.push_back({ y - 1, x });
	ret.push_back({ y + 1, x });
	return ret;
}
vector<array<int, 2>> Neighbor6(int y, int x)
{
	vector<array<int, 2>> ret;
	const int ofs = (y & 1); // 1 - (y & 1); //

	ret.push_back({ y, x - 1 });
	ret.push_back({ y, x + 1 });

	ret.push_back({ y - 1, x - 1 + ofs });
	ret.push_back({ y - 1, x + ofs });

	ret.push_back({ y + 1, x - 1 + ofs });
	ret.push_back({ y + 1, x + ofs });

	return ret;
}
vector<array<int, 2>> Neighbor8(int y, int x)
{
	vector<array<int, 2>> ret;
	ret.push_back({ y - 1, x - 1 });
	ret.push_back({ y - 1, x });
	ret.push_back({ y - 1, x + 1 });

	ret.push_back({ y, x - 1 });
	ret.push_back({ y, x + 1 });

	ret.push_back({ y + 1, x - 1 });
	ret.push_back({ y + 1, x });
	ret.push_back({ y + 1, x + 1 });
	return ret;
}

vector<int> DecompDigit(ull val)
{
	if (val == 0)
		return { 0 };

	vector<int> ret;
	while (val)
	{
		ret.emplace_back(val % 10);
		val /= 10;
	}
	reverse(ret.begin(), ret.end());
	return ret;
}


#define PI 3.14159265358979l
long double R2D = 180 / PI;
long double D2R = PI / 180;

static const ll Mod = 1'000'000'007;
static const ll Mod9 = 998244353;// 1000000007;
static const ll INF64 = 4'500000'000000'000000;// 10000000000000000;
static const int INF32 = 2'000'000'000;

random_device rd;
mt19937 mt(0);//(rd());
#if USE_MP
using mpint = boost::multiprecision::cpp_int;
#endif

struct UnionFind
{
public:
	enum RESULT
	{
		INCONSISTENT = 0,
		CONSISTENT = 1,
		ADDED = 2,
	};

	UnionFind(int size) :
		parent(size),
		nxt(size),
		nodes(size, 1),
		potential(size, 0),
		groups(size)
	{
		std::iota(parent.begin(), parent.end(), 0);
		std::iota(nxt.begin(), nxt.end(), 0);
	}

	/// <summary>
	/// u -> v にduvの辺を張る
	/// 結果はenum RESULTで返すが、失敗(矛盾した辺)を0に割り当てているので
	///   if (!uf.Unite(u,v,duv)) {
	///       // 失敗時の処理
	///   }
	/// みたいな使い方ができる
	/// </summary>
	RESULT Unite(int u, int v, int duv = 0)
	{
		// uの根に対するvの根のポテンシャルを求める
		// 元々pv-puだった(u,v)の関係をduvにしないといけない
		int pu = Potential(u);
		int pv = Potential(v);
		int druv = duv - (pv - pu);

		int ru = Find(u);
		int rv = Find(v);
		if (ru == rv) {
			if (druv == 0)
				return CONSISTENT;
			else
				return INCONSISTENT;
		}
		std::swap(nxt[u], nxt[v]);	// 列挙用リスト更新

		if (nodes[ru] < nodes[rv])	// マージテク
		{
			std::swap(ru, rv);
			druv = -druv;
		}
		groups--;
		nodes[ru] += nodes[rv];
		potential[rv] = druv;
		parent[rv] = ru;
		return ADDED;
	}

	int Find(int v)
	{
		if (parent[v] == v)
			return v;
		else
		{
			int rv = Find(parent[v]);
			nodes[v] = nodes[rv];
			potential[v] += potential[parent[v]];
			return parent[v] = rv;
		}
	}

	inline bool IsUnited(int u, int v)
	{
		return Find(u) == Find(v);
	}

	inline int CountNodes(int v)
	{
		Find(v);
		return nodes[v];
	}

	inline int CountGroups() const
	{
		return groups;
	}

	std::vector<int> GetGroupMember(int v) const
	{
		std::vector<int> ret;
		int w = v;
		do {
			ret.emplace_back(w);
			w = nxt[w];
		} while (w != v);
		return ret;
	}

	long long Potential(int v)
	{
		Find(v);
		return potential[v];
	}

	// uから見たvのポテンシャル
	long long Diff(int u, int v)
	{
		return Potential(v) - Potential(u);
	}

private:
	std::vector<int> parent;	// 親ノード
	std::vector<int> nodes;		// 同じグループに属するノードの数
	std::vector<int> nxt;		// 同じグループに属する次のノード(ループしている)
	std::vector<long long> potential;	// ポテンシャル
	int groups;
};

const double EPS = 1e-10;

struct Solver
{
	int Run()
	{
		int N, M;
		cin >> N >> M;
		if (N == 0 && M == 0)
			return 1;

		UnionFind uf(N);
		for (int i = 0; i < M; i++) {
			char c;
			cin >> c;
			if (c == '!') {
				int u, v, w;
				cin >> u >> v >> w;
				u--, v--;
				uf.Unite(u, v, w);
			}
			else {
				int u, v;
				cin >> u >> v;
				u--, v--;

				if (uf.IsUnited(u, v)) {
					cout << uf.Diff(u, v) << "\n";
				}
				else {
					cout << "UNKNOWN\n";
				}
			}


		}


		return 0;
	}
};

int main()
{
	std::cin.tie(nullptr); //★インタラクティブ注意★
	std::ios_base::sync_with_stdio(false);
	int T = 1;
	//cin >> T;
	while (1)//T--)
	{
		Solver S;
		if (S.Run())
			return 0;
	}

	return 0;
}

/// 値渡しになっていないか?
/// 入力を全部読んでいるか? 途中でreturnしない
/// 32bitで収まるか? 10^5数えるとき
/// modは正しいか?
/// multisetでcountしていないか?
accuracy: 1
time_ms: 200
memory_kb: 5092
final_rank: 2

code_snippet: // #define _GLIBCXX_DEBUG
#include <bits/stdc++.h>
using namespace std;

/* alias*/
using ull = unsigned long long;
using ll = long long;
using vi = vector<int>;
using vll = vector<long long>;
using vs = vector<string>;
using vvi = vector<vi>;
using vvll = vector<vll>;
using vvs = vector<vs>;
using pii = pair<int, int>;
using vpii = vector<pii>;

/* define short */
#define pb push_back
#define mp make_pair
#define reps(i, s, n) for (int i = (s); i < (int)(n); i++)
#define rreps(i, s, n) for (int i = n-1; i >= (s); i--)
#define rep(i, n) reps(i, 0, n)
#define rep1(i, n) reps(i, 1, n+1)
#define rrep(i, n) rreps(i, 0, n)
#define rrep1(i, n) rreps(i, 1, n+1)
#define all(a) (a).begin(), (a).end()
#define rall(a) (a).rbegin(), (a).rend()


/* func */
template<class T> inline bool chmax(T& a, T b) { return ((a < b) ? (a = b, true) : (false)); }
template<class T> inline bool chmin(T& a, T b) { return ((a > b) ? (a = b, true) : (false)); }

template<class T> struct WeightedUnionFind {
  vector<int> par, rank;
  vector<T> diff_weight;

  WeightedUnionFind(int n = 1) {
    init(n);
  }

  void init(int n = 1) {
    par.resize(n); rank.resize(n); diff_weight.resize(n);
    for (int i = 0; i < n; ++i) {
      par[i] = i;
      rank[i] = 0;
      diff_weight[i] = 0;
    }
  }

  int root(int x) {
    if (par[x] == x) return x;
    else {
      int r = root(par[x]);
      diff_weight[x] += diff_weight[par[x]];
      return par[x] = r;
    }
  }

  T weight(int x) {
    root(x);
    return diff_weight[x];
  }

  T diff(int x, int y) {
    return weight(y) - weight(x);
  }

  bool same(int x, int y) {
    return root(x) == root(y);
  }

  bool unite(int x, int y, T w) {
    w += weight(x); w -= weight(y);
    x = root(x); y = root(y);
    if (x == y) return false;
    if (rank[x] < rank[y]) swap(x, y), w = -w;  // ry を rx の子にする
    if (rank[x] == rank[y]) ++rank[x];
    par[y] = x;
    diff_weight[y] = w;
    return true;
  }
};

int main() { 
  int n,m;
  while (true) {
    cin>>n>>m;
    if (n == 0) break;
    WeightedUnionFind<int> uf(n);
    rep(_,m) {
      char s;int a,b,w;
      cin>>s;
      if (s == '!') {
        cin>>a>>b>>w; --a;--b;
        uf.unite(a,b,w);
      }
      if (s == '?') {
        cin>>a>>b; --a;--b;
        if (uf.same(a,b)) cout << uf.diff(a,b) << endl;
        else cout << "UNKNOWN" << endl;
      }
    }
  }
  return 0;
}
accuracy: 1
time_ms: 910
memory_kb: 4316
final_rank: 12

code_snippet: // #define _GLIBCXX_DEBUG 1
#include <bits/stdc++.h>
// #include <atcoder/all>
using namespace std;
#define ll long long
#define ull unsigned long long

#define rep1(i, n) for (int i = 1; i <= n; i++)
#define rep_0(i, n) for (int i = 0; i < n; i++)
#define rep_1(i, s, n) for (int i = s; i < n + s; i++)
#define rep_2(i, s, t, n) for (int i = s; i <= n; i += t)
#define rep_overload(e1, e2, e3, e4, f, ...) f
#define rep(...) rep_overload(__VA_ARGS__, rep_2, rep_1, rep_0)(__VA_ARGS__)
#define repr(i, n) for (int i = n - 1; i >= 0; i--)
#define repr1(i, n) for (int i = n; i > 0; i--)

auto chmax = [](auto& a, auto b) {bool ret=a<b; if(ret)a=b; return ret; };
auto chmin = [](auto& a, auto b) {bool ret=a>b; if(ret)a=b; return ret; };
template <typename T>
using pq_inv = priority_queue<T, vector<T>, greater<T>>;
vector<int> dd4 = {1, 0, -1, 0, 1};                // rep(i,4) nh=h+dd4[i]; nw=w+dd4[i+1];
vector<int> dd8 = {1, 0, -1, 0, 1, 1, -1, -1, 1};  // rep(i,8) nh=h+dd8[i]; nw=w+dd8[i+1];
// using mint = atcoder::modint1000000007;
// using mint = atcoder::modint998244353;

template <typename T>
struct WeightedUnionFind {
    vector<int> par, rank;
    vector<T> diff_weight;
    WeightedUnionFind(int n) {
        init(n);
    }
    void init(int n) {
        par.assign(n, -1);
        rank.assign(n, 0);
        diff_weight.assign(n, 0);
    }
    int root(int x) {
        if (par[x] == -1) return x;
        int r = root(par[x]);
        diff_weight[x] += diff_weight[par[x]];
        return par[x] = r;
    }
    T weight(int x) {
        root(x);
        return diff_weight[x];
    }

    bool is_same(int x, int y) {
        return root(x) == root(y);
    }
    bool merge(int x, int y, T w) {
        w += weight(x) - weight(y);
        x = root(x);
        y = root(y);
        if (x == y) return false;
        if (rank[x] < rank[y]) swap(x, y), w = -w;
        if (rank[x] == rank[y]) rank[x]++;
        par[y] = x;
        diff_weight[y] = w;
        return true;
    }

    T diff(int x, int y) {
        return weight(y) - weight(x);
    }
};

void solve(int N, int M) {
    WeightedUnionFind<ll> wuf(N + 1);
    rep(i, M) {
        char t;
        cin >> t;
        if (t == '!') {
            int x, y, d;
            cin >> x >> y >> d;
            wuf.merge(x, y, d);
        } else {
            int x, y;
            cin >> x >> y;
            if(wuf.is_same(x,y))cout << wuf.diff(x, y) << endl;
            else cout << "UNKNOWN" << endl;
        }
    }
}

int main() {
    std::cin.tie(nullptr);
    // int T;
    // // cin >> T;
    // T = 1;
    // for (int i = 0; i < T; i++) {
    //     // solve(i);
    //     solve();
    // }

    while (true) {
        int N, M;
        cin >> N >> M;
        if (N == 0 && M == 0)
            break;
        else
            (solve(N, M));
    }
    return 0;
}
accuracy: 1
time_ms: 860
memory_kb: 4460
final_rank: 11

code_snippet: #include<bits/stdc++.h>

using namespace std;

#define rep(i, n) for(int i = 0; i < (n); i++)
#define per(i, n) for(int i = (n)-1; i >= 0; i--)
#define rep2(i, l, r) for(int i = (l); i < (r); i++)
#define per2(i, l, r) for(int i = (r)-1; i >= (l); i--)
#define each(e, x) for(auto &e : x)
#define sz(x) (int)x.size()
#define all(x) begin(x), end(x)
#define rall(x) rbegin(x), rend(x)
#define eb emplace_back
#define MM << ' ' <<
#define TT template<typename T>
using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;

TT bool chmin(T &x, T y) { return x > y ? (x = y, true) : false; }

TT bool chmax(T &x, T y) { return x < y ? (x = y, true) : false; }

TT using minheap = priority_queue<T, vector<T>, greater<T>>;
TT using maxheap = priority_queue<T>;

TT void print(vector<T> v, T x = 0) {
    int n = sz(v);
    rep(i, n) cout << v[i] + x << (i == n - 1 ? '\n' : ' ');
    if (n == 0) cout << '\n';
}

TT void printn(vector<T> v, T x = 0) {
    int n = sz(v);
    rep(i, n) cout << v[i] + x << '\n';
}

TT void rearrange(vector<T> &v) {
    sort(all(v));
    v.erase(unique(all(v)), end(v));
}

TT int lb(vector<T> &v, T x) { return lower_bound(all(v), x) - begin(v); }

TT int ub(vector<T> &v, T x) { return upper_bound(all(v), x) - begin(v); }

const int inf = (1 << 30) - 1;
const ll INF = (1LL << 60) - 1;

struct io_setup {
    io_setup() {
        ios_base::sync_with_stdio(false);
        cin.tie(NULL);
        cout << fixed << setprecision(15);
    }
} io_setup;

TT struct weighted_uf {
    vector<int> data;
    vector<T> ws;
    const int n;
    weighted_uf(int n) : data(n, -1), ws(n, 0), n(n) {}

    int root(int x) {
        if (data[x] < 0) return x;
        int par = root(data[x]);
        ws[x] += ws[data[x]];
        return data[x] = par;
    }

    T weight(int x) {
        root(x);
        return ws[x];
    }

    bool unite(int x, int y, T w) {
        w += weight(x), w -= weight(y);
        x = root(x), y = root(y);
        if (x == y) return false;
        data[x] += data[y], data[y] = x;
        ws[y] = w;
        return true;
    }

    T diff(int x, int y) { return weight(y) - weight(x); }

    bool same(int x, int y) { return root(x) == root(y); }
};

int main() {
    while (1) {
        int n, q;
        cin >> n >> q;
        if (!n)
            break;
        weighted_uf<ll> uf(n);
        while (q--) {
            char type;
            ll x, y;
            cin >> type >> x >> y;
            x--, y--;
            if (type == '!') {
                ll w;
                cin >> w;
                uf.unite(x, y, w);
            }
            else {
                if (uf.same(x, y))
                    cout << uf.diff(x, y) << '\n';
                else
                    cout << "UNKNOWN\n";
            }
        }
    }
}
accuracy: 1
time_ms: 200
memory_kb: 6716
final_rank: 6

code_snippet: #include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < n; ++i)
typedef long long ll;
using namespace std;

template <class Abel>
struct UnionFind {
    vector<int> par;
    vector<int> rank;
    vector<Abel> diff_weight;
    UnionFind(int n = 1, Abel SUM_UNITY = 0) {
        init(n, SUM_UNITY);
    }
    void init(int n = 1, Abel SUM_UNITY = 0) {
        par.resize(n);
        rank.resize(n);
        diff_weight.resize(n);
        for (int i = 0; i < n; ++i) par[i] = i, rank[i] = 0, diff_weight[i] = SUM_UNITY;
    }
    int root(int x) {
        if (par[x] == x) {
            return x;
        } else {
            int r = root(par[x]);
            diff_weight[x] += diff_weight[par[x]];
            return par[x] = r;
        }
    }
    Abel weight(int x) {
        root(x);
        return diff_weight[x];
    }
    bool issame(int x, int y) {
        return root(x) == root(y);
    }
    bool merge(int x, int y, Abel w) {
        w += weight(x);
        w -= weight(y);
        x = root(x);
        y = root(y);
        if (x == y) return false;
        if (rank[x] < rank[y]) swap(x, y), w = -w;
        if (rank[x] == rank[y]) ++rank[x];
        par[y] = x;
        diff_weight[y] = w;
        return true;
    }
    Abel diff(int x, int y) {
        return weight(y) - weight(x);
    }
};

void solve(int N, int M) {
    UnionFind<ll> uf(N);
    while (M--) {
        char c;
        cin >> c;
        if (c == '!') {
            int l, r;
            ll w;
            cin >> l >> r >> w;
            l--, r--;
            uf.merge(l, r, w);
        } else if (c == '?') {
            int l, r;
            cin >> l >> r;
            l--, r--;

            if (uf.issame(l, r)) cout << uf.diff(l, r) << "\n";
            else cout << "UNKNOWN\n";
        }
    }
}

int main() {
    cin.tie(0)->sync_with_stdio(0);

    int N, M;
    while (cin >> N >> M, N) solve(N, M);

    return 0;
}
accuracy: 1
time_ms: 200
memory_kb: 4552
final_rank: 1

code_snippet: #include <bits/stdc++.h>
using namespace std;
#include <iostream>
#include <vector>
using ll = long long;
using ld = long double;
using Graph = vector<vector<int>>;
using vi = vector<int>;
using vl = vector<long>;
using vll = vector<long long>;
using vb = vector<bool>;
using vvi = vector<vi>;
using vvl = vector<vl>;
using vvb = vector<vb>;
using vvvb = vector<vvb>;
using vvll = vector<vll>;
using vvvll = vector<vvll>;
using vd = vector<double>;
using vvd = vector<vd>;
using vvvd = vector<vvd>;
using vc = vector<char>;
using vvc = vector<vc>;
using lll = __int128_t;
using vs = vector<string>;
using pii = pair<long long, long long>;
const long double EPS = 1e-10;
const double INF = 1e18;

const long double PI = acos(-1.0L);
#define mp make_pair
#define reps(i, a, n) for (ll i = (a); i < (ll)(n); i++)
#define rep(i, n) for (ll i = (0); i < (ll)(n); i++)
#define rrep(i, n) for (ll i = (1); i < (ll)(n + 1); i++)
#define repd(i, n) for (ll i = n - 1; i >= 0; i--)
#define rrepd(i, n) for (ll i = n; i >= 1; i--)
#define ALL(n) n.begin(), n.end()
#define rALL(n) n.rbegin(), n.rend()
#define fore(i, a) for (auto &i : a)
#define IN(a, x, b) (a <= x && x < b)
#define IN(a, x, b) (a <= x && x < b)
#define INIT                        \
  std::ios::sync_with_stdio(false); \
  std::cin.tie(0);
template <class T>
inline T CHMAX(T &a, const T b) {
  return a = (a < b) ? b : a;
}
template <class T>
inline T CHMIN(T &a, const T b) {
  return a = (a > b) ? b : a;
}
#include <algorithm>  // min, max, swap, sort, reverse, lower_bound, upper_bound
#include <bitset>     // bitset
#include <cctype>     // isupper, islower, isdigit, toupper, tolower
#include <cstdint>    // int64_t, int*_t
#include <cstdio>     // printf
#include <deque>      // deque
#include <iostream>   // cout, endl, cin
#include <map>        // map
#include <queue>      // queue, priority_queue
#include <set>        // set
#include <stack>      // stack
#include <string>     // string, to_string, stoi
#include <tuple>      // tuple, make_tuple
#include <unordered_map>  // unordered_map
#include <unordered_set>  // unordered_set
#include <utility>        // pair, make_pair
#include <vector>         // vector
using namespace std;

#include <type_traits>

#define LOOP(n) REPI($_, (n))

#define REPI(i, n) \
  for (int i = 0, i##_length = static_cast<int>(n); i < i##_length; ++i)
#define REPF(i, l, r)                                                        \
  for (std::common_type_t<decltype(l), decltype(r)> i = (l), i##_last = (r); \
       i < i##_last; ++i)
#define REPIS(i, l, r, s)                                        \
  for (std::common_type_t<decltype(l), decltype(r), decltype(s)> \
           i = (l),                                              \
           i##_last = (r);                                       \
       i < i##_last; i += (s))

#define REPR(i, n) for (auto i = (n); --i >= 0;)
#define REPB(i, l, r)                                                        \
  for (std::common_type_t<decltype(l), decltype(r)> i = (r), i##_last = (l); \
       --i >= i##_last;)
#define REPRS(i, l, r, s)                                        \
  for (std::common_type_t<decltype(l), decltype(r), decltype(s)> \
           i = (l) + ((r) - (l)-1) / (s) * (s),                  \
           i##_last = (l);                                       \
       i >= i##_last; (i -= (s)))

#define REP(...) $OVERLOAD4(__VA_ARGS__, REPIS, REPF, REPI, LOOP)(__VA_ARGS__)
#define REPD(...) $OVERLOAD4(__VA_ARGS__, REPRS, REPB, REPR)(__VA_ARGS__)

#define FORO(i, n) \
  for (int i = 0, i##_last = static_cast<int>(n); i <= i##_last; ++i)
#define FORI(i, l, r)                                                        \
  for (std::common_type_t<decltype(l), decltype(r)> i = (l), i##_last = (r); \
       i <= i##_last; ++i)
#define FORIS(i, l, r, s)                                        \
  for (std::common_type_t<decltype(l), decltype(r), decltype(s)> \
           i = (l),                                              \
           i##_last = (r);                                       \
       i <= i##_last; i += (s))

#define FORRO(i, n) for (auto i = (n); i >= 0; --i)
#define FORR(i, l, r)                                                        \
  for (std::common_type_t<decltype(l), decltype(r)> i = (r), i##_last = (l); \
       i >= i##_last; --i)
#define FORRS(i, l, r, s)                                        \
  for (std::common_type_t<decltype(l), decltype(r), decltype(s)> \
           i = (l) + ((r) - (l)) / (s) * (s),                    \
           i##_last = (l);                                       \
       i >= i##_last; i -= (s))

#define FOR(...) $OVERLOAD4(__VA_ARGS__, FORIS, FORI, FORO)(__VA_ARGS__)
#define FORD(...) $OVERLOAD4(__VA_ARGS__, FORRS, FORR, FORRO)(__VA_ARGS__)

#define ITR1(e0, v) for (const auto &e0 : (v))
#define ITRP1(e0, v) for (auto e0 : (v))
#define ITRR1(e0, v) for (auto &e0 : (v))

#define ITR2(e0, e1, v) for (const auto [e0, e1] : (v))
#define ITRP2(e0, e1, v) for (auto [e0, e1] : (v))
#define ITRR2(e0, e1, v) for (auto &[e0, e1] : (v))

#define ITR3(e0, e1, e2, v) for (const auto [e0, e1, e2] : (v))
#define ITRP3(e0, e1, e2, v) for (auto [e0, e1, e2] : (v))
#define ITRR3(e0, e1, e2, v) for (auto &[e0, e1, e2] : (v))

#define ITR4(e0, e1, e2, e3, v) for (const auto [e0, e1, e2, e3] : (v))
#define ITRP4(e0, e1, e2, e3, v) for (auto [e0, e1, e2, e3] : (v))
#define ITRR4(e0, e1, e2, e3, v) for (auto &[e0, e1, e2, e3] : (v))

#define ITR(...) $OVERLOAD5(__VA_ARGS__, ITR4, ITR3, ITR2, ITR1)(__VA_ARGS__)
#define ITRP(...) \
  $OVERLOAD5(__VA_ARGS__, ITRP4, ITRP3, ITRP2, ITRP1)(__VA_ARGS__)
#define ITRR(...) \
  $OVERLOAD5(__VA_ARGS__, ITRR4, ITRR3, ITRR2, ITRR1)(__VA_ARGS__)
std::ostream &operator<<(std::ostream &dest, __int128_t value) {
  std::ostream::sentry s(dest);
  if (s) {
    __uint128_t tmp = value < 0 ? -value : value;
    char buffer[128];
    char *d = std::end(buffer);
    do {
      --d;
      *d = "0123456789"[tmp % 10];
      tmp /= 10;
    } while (tmp != 0);
    if (value < 0) {
      --d;
      *d = '-';
    }
    int len = std::end(buffer) - d;
    if (dest.rdbuf()->sputn(d, len) != len) {
      dest.setstate(std::ios_base::badbit);
    }
  }
  return dest;
}
template <class Type>
class WeightedUnionFind {
 public:
  WeightedUnionFind() = default;

  /// @brief 重み付き Union-Find 木を構築します。
  /// @param n 要素数
  explicit WeightedUnionFind(size_t n)
      : m_parentsOrSize(n, -1), m_diffWeights(n) {}

  /// @brief 頂点 i の root のインデックスを返します。
  /// @param i 調べる頂点のインデックス
  /// @return 頂点 i の root のインデックス
  int find(int i) {
    if (m_parentsOrSize[i] < 0) {
      return i;
    }

    const int root = find(m_parentsOrSize[i]);

    m_diffWeights[i] += m_diffWeights[m_parentsOrSize[i]];

    // 経路圧縮
    return (m_parentsOrSize[i] = root);
  }

  /// @brief a のグループと b のグループを統合します。
  /// @param a 一方のインデックス
  /// @param b 他方のインデックス
  /// @param w (b の重み) - (a の重み)
  /// a を含むグループと b を含むグループを併合し、b の重みは a と比べて w
  /// 大きくなるようにする
  void merge(int a, int b, Type w) {
    w += weight(a);
    w -= weight(b);

    a = find(a);
    b = find(b);

    if (a != b) {
      // union by size (小さいほうが子になる）
      if (-m_parentsOrSize[a] < -m_parentsOrSize[b]) {
        std::swap(a, b);
        w = -w;
      }

      m_parentsOrSize[a] += m_parentsOrSize[b];
      m_parentsOrSize[b] = a;
      m_diffWeights[b] = w;
    }
  }

  /// @brief (b の重み) - (a の重み) を返します。
  /// @param a 一方のインデックス
  /// @param b 他方のインデックス
  /// @remark a と b が同じグループに属さない場合の結果は不定です。
  /// @return (b の重み) - (a の重み)
  ///(b の重み) - (a の重み) を返す。a と b
  /// が同じグループに属さない場合の結果は不定
  Type diff(int a, int b) { return (weight(b) - weight(a)); }

  /// @brief a と b が同じグループに属すかを返します。
  /// @param a 一方のインデックス
  /// @param b 他方のインデックス
  /// @return a と b が同じグループに属す場合 true, それ以外の場合は false
  /// a と b が同じグループに属すかを返す
  bool connected(int a, int b) { return (find(a) == find(b)); }

  /// @brief i が属するグループの要素数を返します。
  /// @param i インデックス
  /// @return i が属するグループの要素数
  int size(int i) { return -m_parentsOrSize[find(i)]; }

 private:
  // m_parentsOrSize[i] は i の 親,
  // ただし root の場合は (-1 * そのグループに属する要素数)
  std::vector<int> m_parentsOrSize;

  // 重み
  std::vector<Type> m_diffWeights;

  Type weight(int i) {
    find(i);  // 経路圧縮
    return m_diffWeights[i];
  }
};

__int128 parse(string &s) {
  __int128 ret = 0;
  for (int i = 0; i < s.length(); i++)
    if ('0' <= s[i] && s[i] <= '9') ret = 10 * ret + s[i] - '0';
  return ret;
}

ll GCD(ll m, ll n) {
  // ベースケース
  if (n == 0) return m;

  // 再帰呼び出し
  return GCD(n, m % n);
}

ll minlong = 0;

long long Power(long long a, long long b, long long m) {
  long long p = a, Answer = 1;
  for (int i = 0; i < 63; i++) {
    ll wari = (1LL << i);
    if ((b / wari) % 2 == 1) {
      Answer %= m;
      Answer = (Answer * p) % m;  // 「a の 2^i 乗」が掛けられるとき
    }
    p %= m;
    p = (p * p) % m;
  }
  return Answer;
}

// a ÷ b を m で割った余りを返す関数
long long Division(long long a, long long b, long long m) {
  return (a * Power(b, m - 2, m)) % m;
}

// nCr mod 998244353 を返す関数
long long nCk(ll n, ll r) {
  const long long M = 998244353;

  // 手順 1: 分子 a を求める
  long long a = 1;
  for (int i = 1; i <= n; i++) a = (a * i) % M;

  // 手順 2: 分母 b を求める
  long long b = 1;
  for (int i = 1; i <= r; i++) b = (b * i) % M;
  for (int i = 1; i <= n - r; i++) b = (b * i) % M;

  // 手順 3: 答えを求める
  return Division(a, b, M);
}
using Interval = pair<ll, ll>;
// 終点時間でsortをかけるのに必要(区間スケジューリング問題など)
bool cmp(const Interval &a, const Interval &b) { return a.second < b.second; }
vll dycstra(vector<vector<pair<ll, ll>>> G, ll N, ll K) {
  vb kaku(N, false);
  vll cur(N, INF);
  cur[K] = 0;
  priority_queue<pair<ll, ll>, vector<pair<ll, ll>>, greater<pair<ll, ll>>> Q;
  Q.push(make_pair(cur[K], K));
  while (!Q.empty()) {
    ll pos = Q.top().second;
    Q.pop();
    if (kaku[pos]) continue;
    kaku[pos] = true;
    for (ll i = 0; i < G[pos].size(); i++) {
      ll nex = G[pos][i].first;
      ll cost = G[pos][i].second;
      if (cur[nex] > cur[pos] + cost) {
        cur[nex] = cur[pos] + cost;
        Q.push(make_pair(cur[nex], nex));
      }
    }
  }
  return cur;
}
template <typename T>
void Print(vector<T> &A) {
  rep(i, A.size()) { cout << A[i] << ' '; }
  cout << endl;
}
template <typename xy>
class BIT {
 public:
  BIT() = default;

  // 長さ size の数列で初期化
  explicit BIT(size_t size) : m_bit(size + 1) {}

  // 数列で初期化
  explicit BIT(const std::vector<xy> &v) : BIT(v.size()) {
    for (int i = 0; i < v.size(); ++i) {
      add((i + 1), v[i]);
    }
  }

  // 閉区間 [1, r] の合計を返す (1-based indexing)
  xy sum(int r) const {
    xy ret = 0;

    for (; 0 < r; r -= (r & -r)) {
      ret += m_bit[r];
    }

    return ret;
  }

  // 閉区間 [l, r] の合計を返す (1-based indexing)
  xy sum(int l, int r) const { return (sum(r) - sum(l - 1)); }

  // 数列の i 番目の要素を加算 (1-based indexing)
  void add(int i, xy value) {
    for (; i < m_bit.size(); i += (i & -i)) {
      m_bit[i] += value;
    }
  }

 private:
  std::vector<xy> m_bit;
};

// Binary Indexed Tree (1.2 区間加算対応)
// 1-based indexing
template <typename x>
class BIT_RAQ {
 public:
  BIT_RAQ() = default;

  explicit BIT_RAQ(size_t size) : m_bit0(size), m_bit1(size) {}

  explicit BIT_RAQ(const std::vector<x> &v) : m_bit0(v), m_bit1(v.size()) {}

  // 閉区間 [1, r] の合計を返す (1-based indexing)
  x sum(int r) const { return (m_bit0.sum(r) + m_bit1.sum(r) * r); }

  // 閉区間 [l, r] の合計を返す (1-based indexing)
  x sum(int l, int r) const { return (sum(r) - sum(l - 1)); }

  // 数列の i 番目の要素を加算 (1-based indexing)
  void add(int i, x value) { m_bit0.add(i, value); }

  // 閉区間 [l, r] の要素を加算 (1-based indexing)
  void add(int l, int r, x value) {
    x a = (-value * (l - 1)), b = (value * r);
    m_bit0.add(l, a);
    m_bit0.add((r + 1), b);
    m_bit1.add(l, value);
    m_bit1.add((r + 1), -value);
  }

 private:
  BIT<x> m_bit0;

  BIT<x> m_bit1;
};
vll BFS(vvll &G, ll &x) {
  vll cut(G.size(), INF);
  queue<ll> Q;
  Q.push(x);
  cut[x] = 0;
  while (!Q.empty()) {
    ll a = Q.front();
    Q.pop();
    rep(i, G[a].size()) {
      if (cut[G[a][i]] > cut[a] + 1) {
        cut[G[a][i]] = cut[a] + 1;
        Q.push(G[a][i]);
      }
    }
  }
  return cut;
}

void Yes(bool b) {
  if (b)
    cout << "Yes" << endl;
  else
    cout << "No" << endl;
}
vector<long long> yaku(long long n) {
  vector<long long> ret;
  for (long long i = 1; i * i <= n; i++) {
    if (n % i == 0) {
      ret.push_back(i);
      if (i * i != n) ret.push_back(n / i);
    }
  }
  sort(ret.begin(), ret.end());  // 昇順に並べる
  return ret;
}

priority_queue<pair<ll, ll>, vector<pair<ll, ll>>, greater<pair<ll, ll>>> Q;

double randouble() { return 1.0 * rand() / RAND_MAX; }
struct Mo {
  int n;
  vector<pair<int, int>> lr;

  explicit Mo(int n) : n(n) {}  // nの値の設定を行う。

  void add(int l, int r) { /* [l, r) */
    lr.push_back({l, r});
  }

  template <typename AL, typename AR, typename EL, typename ER, typename O>
  void build(const AL &add_left, const AR &add_right, const EL &erase_left,
             const ER &erase_right, const O &out) {
    int q = (int)lr.size();  // lr.size()はクエリの数に等しい。
    int bs = n / min<int>(n, sqrt(q));  // 事実上bs>0となるように設定、
    // cout << bs << endl;
    vector<int> ord(q);
    iota(begin(ord), end(ord), 0);
    sort(begin(ord), end(ord), [&](int a, int b) {
      int ablock = lr[a].first / bs, bblock = lr[b].first / bs;
      if (ablock != bblock) return ablock < bblock;  //
      return (ablock & 1) ? lr[a].second > lr[b].second
                          : lr[a].second < lr[b].second;
      // 偶奇で昇順・降順を分けることによって計算量が定数倍高速化する。
    });
    int l = 0, r = 0;
    for (auto idx : ord) {
      //  cout << idx << endl;
      while (l > lr[idx].first) add_left(--l);
      while (r < lr[idx].second) add_right(r++);
      while (l < lr[idx].first) erase_left(l++);
      while (r > lr[idx].second) erase_right(--r);
      out(idx);
    }
  }

  template <typename A, typename E, typename O>
  void build(const A &add, const E &erase, const O &out) {
    build(add, add, erase, erase, out);
  }
};

struct TRI {
  ll a;
  ll b;
  ll c;
};
bool operator>(const TRI &r, const TRI &l) {
  return (r.a > l.a | (r.a == l.a & r.b > l.b) |
          (r.a == l.a & r.b == l.b & r.c > l.c));
}
bool operator<(const TRI &r, const TRI &l) {
  return (r.a < l.a | (r.a == l.a & r.b < l.b) |
          (r.a == l.a & r.b == l.b & r.c < l.c));
}
struct TR {
  ll a;
  ll b;
  ll c;
  ll d;
};
bool operator>(const TR &r, const TR &l) {
  return (r.a > l.a | (r.a == l.a & r.b > l.b) |
          (r.a == l.a & r.b == l.b & r.c > l.c));
}
bool operator<(const TR &r, const TR &l) {
  return (r.a < l.a | (r.a == l.a & r.b < l.b) |
          (r.a == l.a & r.b == l.b & r.c < l.c));
}
// Sはdataを表している。
using S = ll;
// Fはlazy(遅延伝搬用)を表している
using F = ll;
// 区間取得をどのようにするかを定義する。RMQだったらmin(a,b)とかになる。
S op(S a, S b) { return a + b; };
// op(e,a)=op(a,e)=aとなるような単位元を定義する。RMQの場合はINFが安牌
S e() { return 0; }
// 親のlazyが子のdataに対してどの様に作用させるかを定義する。区間加算をする場合はただ足せば良い
S mapping(F f, S x) { return x + f; }
/*親のlazyが子のlazyに対してどの様に作用させるかを定義する。区間加算をする場合はただ足せば良い
ただ可変では無い場合fが後の操作である事を留意しておくと良い*/
F composition(F f, F g) { return f + g; }
// mapping(a,id)=aとなる様なidを設定すれば良い今回の区間加算の場合は0が適する。区間乗算の場合は1が適する
F id() { return 0; }
ll half = 499122177;
// 条件を満たす場合オイラーツアーを考えれば出来る。
// 重要なのは求める次数を満たす辺を構築すること
// G:グラフ,P:オイラーツアーで探索した際に通った辺の番号の配列,in,out:最初と最後に通った時刻
// U[辺の番号]={親,現在いる位置}正の値だったら終点、それ以外親を出力することで頂点で表した経路の復元が可能
// num=辺の番号を記録,cou=時刻を記録

// 返り値: a と b の最大公約数
// ax + by = gcd(a, b) を満たす (x, y) が格納される
long long extGCD(long long a, long long b, long long &x, long long &y) {
  if (b == 0) {
    x = 1;
    y = 0;
    return a;
  }
  long long d = extGCD(b, a % b, y, x);
  y -= a / b * x;
  return d;
}
vll toposo(vvll &G) {
  // 帰りがけでやりたい場合はstack、幅優先でやりたい場合はqueueで行う
  stack<ll> Q;
  ll n = G.size();
  vll A(n, 0);
  rep(i, n) {
    rep(j, G[i].size()) { A[G[i][j]]++; }
  }
  vb han(n, false);
  rep(i, n) {
    if (A[i] == 0) {
      Q.push(i);
      han[i] = true;
    }
  }
  vll B;
  while (!Q.empty()) {
    ll a = Q.top();
    Q.pop();
    B.push_back(a);
    rep(i, G[a].size()) {
      if (!han[G[a][i]]) {
        A[G[a][i]]--;
        if (A[G[a][i]] == 0) {
          Q.push(G[a][i]);
          han[G[a][i]] = true;
        }
      }
    }
  }
  if (B.size() != n) {
    vll C(n, -2);
    return C;
  }
  return B;
}
#include <bits/stdc++.h>
// ax+bのようなグラフの最小値をクエリ一個あたりO(1)で算出することが出来る。
// 条件は２つ存在する。
// 1.追加される順に傾きは単調減少でなければならない(傾きに関してソートする必要性が生じる)
// 2.追加される順にxは単調増加でなければならない(クエリ先読み等のひと手間が必要になる可能性がある)
// ２つの条件が満たせない場合はこのサンプルを使うことが出来ない(クエリが２種類存在し、直線の追加とxを指定して最小値を求める動作を平行して行う場合等)

struct ConvexHullTrick {
  std::deque<std::pair<long long, long long>> dq;  // （傾き、切片）

  void add(long long a, long long b) {
    std::pair<long long, long long> p0 = std::make_pair(a, b);
    while (dq.size() >= 2) {
      int sz = dq.size();
      std::pair<long long, long long> p1 = dq[sz - 1];
      std::pair<long long, long long> p2 = dq[sz - 2];
      if ((p0.second - p1.second) * (p0.first - p2.first) <
          (p0.second - p2.second) * (p0.first - p1.first))
        break;  // 交点のx座標を比較
      dq.pop_back();
    }
    dq.push_back(p0);
  }

  long long query(long long x) {
    while (dq.size() >= 2) {
      long long v1 = dq[0].first * x + dq[0].second;
      long long v2 = dq[1].first * x + dq[1].second;
      if (v1 <= v2) break;
      dq.pop_front();
    }
    return dq[0].first * x + dq[0].second;
  }
};
// if(a <= b + EPS) // a <= b
// if(a < b - EPS) // a < b
// if(abs(a - b) < EPS) // a == b
// 頂点 s から頂点 t までの最大フローの総流量を返す
vector<vector<pair<ll, ll>>> G;
ll K;
vb han;
ll ans;
ll N;
vll X;
vector<vector<pair<ll, ll>>> G1;
void dfs(ll cou, ll poi) {
  if (cou == ((1LL << N) - 1)) {
    ans = min(ans, poi);
  }
  for (auto u : G1[cou]) {
    dfs(cou + (1LL << u.first), (poi + u.second) % K);
  }
  return;
}
int main() {
  cout << fixed << setprecision(20);
  ll M;
  ll a = 0, b = 0;
  ll c = 0;
  ll p = 1;
  string S, T;
  ll H, W;
  ll t;
  char O;
  while (1) {
    cin >> N >> M;
    if (N == 0 && M == 0) return 0;
    vll A(M), B(M), C(M);
    vb han(N, false);
    WeightedUnionFind<ll> U(N);
    rep(i, M) {
      cin >> O;
      if (O == '!') {
        cin >> A[i] >> B[i] >> C[i], --A[i], --B[i];
        if (!U.connected(A[i], B[i])) {
          U.merge(A[i], B[i], C[i]);
        }
      } else {
        cin >> A[i] >> B[i], --A[i], --B[i];
        if (U.connected(A[i], B[i])) {
          cout << U.diff(A[i], B[i]) << endl;

        } else {
          cout << "UNKNOWN" << endl;
        }
      }
    }
  }
}
accuracy: 1
time_ms: 920
memory_kb: 6768
final_rank: 19

code_snippet: #include <bits/stdc++.h>
// #include <atcoder/modint>
#define rng(a) a.begin(),a.end()
#define rrng(a) a.rbegin(),a.rend()
#define INF 2000000000000000000
#define ull unsigned long long
#define ll long long
#define ld long double
#define pll pair<ll, ll>
using namespace std;
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return 1; } return 0; }
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return 1; } return 0; }

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  while (true) {
    
    ll N, M;
    cin >> N >> M;
    if (N == 0 && M == 0) {
      break;
    }
    vector<vector<ll>> child(N);
    vector<ll> par(N);
    for (ll i = 0; i < N; ++i) {
      par.at(i) = i;
    }
    vector<ll> X(N, 0);
    for (ll q = 0; q < M; ++q) {
      char c;
      ll a, b, w;
      cin >> c >> a >> b;
      a -= 1, b -= 1;
      bool same = (par.at(a) == par.at(b));
      if (c == '?') {
        if (same) {
          cout << X.at(b) - X.at(a) << "\n";
        }
        else {
          cout << "UNKNOWN" << "\n";
        }
      }
      else {
        cin >> w;
        if (same) {
          continue;
        }
        ll a_size = child.at(par.at(a)).size();
        ll b_size = child.at(par.at(b)).size();
        if (a_size < b_size) {
          swap(a, b);
          swap(a_size, b_size);
          w = -w;
        }
        ll dx = X.at(a) + w - X.at(b);
        ll par_b = par.at(b);
        for (ll i = 0; i < child.at(par_b).size(); ++i) {
          ll cb = child.at(par_b).at(i);
          child.at(par.at(a)).push_back(cb);
          par.at(cb) = par.at(a);
          X.at(cb) += dx;
        }
        child.at(par.at(a)).push_back(par_b);
        par.at(par_b) = par.at(a);
        X.at(par_b) += dx;
      }
    }
  }
}
accuracy: 1
time_ms: 210
memory_kb: 8604
final_rank: 9

code_snippet: #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
using ull = unsigned long long;
typedef pair<ll,ll> pll;
typedef vector<ll> vll;
typedef vector<pll> vpll;
template<class T> using pqmin = priority_queue<T, vector<T>, greater<T>>;
template<class T> using pqmax = priority_queue<T>;
const ll inf=LLONG_MAX/3;
const ll dx[] = {0, 1, 0, -1, 1, -1, 1, -1};
const ll dy[] = {1, 0, -1, 0, 1, 1, -1, -1};
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define fi first
#define se secondf
#define all(x) x.begin(),x.end()
#define si(x) ll(x.size())
#define rep(i,n) for(ll i=0;i<n;i++)
#define per(i,n) for(ll i=n-1;i>=0;i--)
#define rng(i,l,r) for(ll i=l;i<r;i++)
#define gnr(i,l,r) for(ll i=r-1;i>=l;i--)
#define fore(i, a) for(auto &&i : a)
#define fore2(a, b, v) for(auto &&[a, b] : v)
#define fore3(a, b, c, v) for(auto &&[a, b, c] : v)
template<class T> bool chmin(T& a, const T& b){ if(a <= b) return 0; a = b; return 1; }
template<class T> bool chmax(T& a, const T& b){ if(a >= b) return 0; a = b; return 1; }
template<class T, class U> bool chmin(T& a, const U& b){ return chmin(a, (T)b); }
template<class T, class U> bool chmax(T& a, const U& b){ return chmax(a, (T)b); }
#define LL(...) ll __VA_ARGS__;in(__VA_ARGS__)
#define STR(...) string __VA_ARGS__;in(__VA_ARGS__)
#define CHR(...) char __VA_ARGS__;in(__VA_ARGS__)
#define vec(type,name,...) vector<type>name(__VA_ARGS__)
#define VEC(type,name,size) vector<type>name(size);in(name)
#define VLL(name,size) vector<ll>name(size);in(name)
#define vv(type,name,h,...) vector<vector<type>> name(h,vector<type>(__VA_ARGS__))
#define VV(type,name,h,w) vector<vector<type>> name(h,vector<type>(w));in(name)
#define vvv(type,name,h,w,...) vector<vector<vector<type>>> name(h,vector<vector<type>>(w,vector<type>(__VA_ARGS__)))
#define SUM(...) accumulate(all(__VA_ARGS__),0LL)
template<class T> auto min(const T& a){ return *min_element(all(a)); }
template<class T> auto max(const T& a){ return *max_element(all(a)); }
template<class T, class F = less<>> void sor(T& a, F b = F{}){ sort(all(a), b); }
template<class T> void uniq(T& a){ sor(a); a.erase(unique(all(a)), end(a)); }
void outb(bool x){cout<<(x?"Yes":"No")<<"\n";}
ll max(int x, ll y) { return max((ll)x, y); }
ll max(ll x, int y) { return max(x, (ll)y); }
int min(int x, ll y) { return min((ll)x, y); }
int min(ll x, int y) { return min(x, (ll)y); }
#define lb(c, x) distance((c).begin(), lower_bound(all(c), (x)))
#define lbg(c, x) distance((c).begin(), lower_bound(all(c), (x), greater{}))
#define ub(c, x) distance((c).begin(), upper_bound(all(c), (x)))
#define ubg(c, x) distance((c).begin(), upper_bound(all(c), (x), greater{}))
ll gcd(ll a,ll b){return (b?gcd(b,a%b):a);}
vector<pll> factor(ull x){ vector<pll> ans; for(ull i = 2; i * i <= x; i++) if(x % i == 0){ ans.push_back({i, 1}); while((x /= i) % i == 0) ans.back().second++; } if(x != 1) ans.push_back({x, 1}); return ans; }
vector<ll> divisor(ull x){ vector<ll> ans; for(ull i = 1; i * i <= x; i++) if(x % i == 0) ans.push_back(i); per(i,ans.size() - (ans.back() * ans.back() == x)) ans.push_back(x / ans[i]); return ans; }
vll prime_table(ll n){vec(ll,isp,n+1,1);vll res;rng(i,2,n+1)if(isp[i]){res.pb(i);for(ll j=i*i;j<=n;j+=i)isp[j]=0;}return res;}

template <class T, class S> pair<T, S> operator-(const pair<T, S> &x) { return pair<T, S>(-x.first, -x.second); }
template <class T, class S> pair<T, S> operator-(const pair<T, S> &x, const pair<T, S> &y) { return pair<T, S>(x.fi - y.fi, x.se - y.se); }
template <class T, class S> pair<T, S> operator+(const pair<T, S> &x, const pair<T, S> &y) { return pair<T, S>(x.fi + y.fi, x.se + y.se); }
template <class T> pair<T, T> operator&(const pair<T, T> &l, const pair<T, T> &r) { return pair<T, T>(max(l.fi, r.fi), min(l.se, r.se)); }
template <class T, class S> pair<T, S> operator+=(pair<T, S> &l, const pair<T, S> &r) { return l = l + r; }
template <class T, class S> pair<T, S> operator-=(pair<T, S> &l, const pair<T, S> &r) { return l = l - r; }
template <class T> bool intersect(const pair<T, T> &l, const pair<T, T> &r) { return (l.se < r.se ? r.fi < l.se : l.fi < r.se); }

template <class T> vector<T> &operator++(vector<T> &v) {
		fore(e, v) e++;
		return v;
}
template <class T> vector<T> operator++(vector<T> &v, int) {
		auto res = v;
		fore(e, v) e++;
		return res;
}
template <class T> vector<T> &operator--(vector<T> &v) {
		fore(e, v) e--;
		return v;
}
template <class T> vector<T> operator--(vector<T> &v, int) {
		auto res = v;
		fore(e, v) e--;
		return res;
}
template <class T> vector<T> &operator+=(vector<T> &l, const vector<T> &r) {
		fore(e, r) l.eb(e);
		return l;
}

template<class... Ts> void in(Ts&... t);
[[maybe_unused]] void print(){}
template<class T, class... Ts> void print(const T& t, const Ts&... ts);
template<class... Ts> void out(const Ts&... ts){ print(ts...); cout << '\n'; }
namespace IO{
#define VOID(a) decltype(void(a))
struct S{ S(){ cin.tie(nullptr)->sync_with_stdio(0); fixed(cout).precision(12); } }S;
template<int I> struct P : P<I-1>{};
template<> struct P<0>{};
template<class T> void i(T& t){ i(t, P<3>{}); }
void i(vector<bool>::reference t, P<3>){ int a; i(a); t = a; }
template<class T> auto i(T& t, P<2>) -> VOID(cin >> t){ cin >> t; }
template<class T> auto i(T& t, P<1>) -> VOID(begin(t)){ for(auto&& x : t) i(x); }
template<class T, size_t... idx> void ituple(T& t, index_sequence<idx...>){ in(get<idx>(t)...); }
template<class T> auto i(T& t, P<0>) -> VOID(tuple_size<T>{}){ ituple(t, make_index_sequence<tuple_size<T>::value>{}); }
template<class T> void o(const T& t){ o(t, P<4>{}); }
template<size_t N> void o(const char (&t)[N], P<4>){ cout << t; }
template<class T, size_t N> void o(const T (&t)[N], P<3>){ o(t[0]); for(size_t i = 1; i < N; i++){ o(' '); o(t[i]); } }
template<class T> auto o(const T& t, P<2>) -> VOID(cout << t){ cout << t; }
template<class T> auto o(const T& t, P<1>) -> VOID(begin(t)){ bool first = 1; for(auto&& x : t) { if(first) first = 0; else o(' '); o(x); } }
template<class T, size_t... idx> void otuple(const T& t, index_sequence<idx...>){ print(get<idx>(t)...); }
template<class T> auto o(T& t, P<0>) -> VOID(tuple_size<T>{}){ otuple(t, make_index_sequence<tuple_size<T>::value>{}); }
#undef VOID
}
#define unpack(a) (void)initializer_list<int>{(a, 0)...}
template<class... Ts> void in(Ts&... t){ unpack(IO::i(t)); }
template<class T, class... Ts> void print(const T& t, const Ts&... ts){ IO::o(t); unpack(IO::o((cout << ' ', ts))); }
#undef unpack

struct WeightedUnionFind{
	vll par,wdiff;
	WeightedUnionFind(ll n=-1){
		init(n);
	}
	void init(ll n){
		par.resize(n);
		wdiff.resize(n);
		rep(i,n){
			par[i]=i;
			wdiff[i]=0;
		}
	}
	ll root(ll x){
		if(par[x]==x){
			return x;
		}
		ll r=root(par[x]);
		wdiff[x]+=wdiff[par[x]];
		par[x]=r;
		return r;
	}
	bool same(ll x,ll y){
		return root(x)==root(y);
	}
	void merge(ll x,ll y,ll w){
		//w[x]==w[y]+w
		ll rx=root(x); //w[x]=w[rx]+wdiff[x]
		ll ry=root(y); //w[y]=w[ry]+wdiff[y]
		wdiff[rx]=w-wdiff[x]+wdiff[y]; //w[rx]=w[ry]-wfidd[x]+wdiff[y]+w
		par[rx]=ry;
	}
	ll weight(ll x){
		root(x);
		return wdiff[x];
	}
	ll diff(ll x,ll y){
		return weight(x)-weight(y);
	}
};
int main(){
	cin.tie(0);
	ios::sync_with_stdio(0);
	while(1){
		LL(n,m);
		if(n==0)break;
		WeightedUnionFind uf(n);
		rep(_,m){
			CHR(t);
			if(t=='!'){
				LL(a,b,w);
				a--,b--;
				uf.merge(b,a,w);
			}
			else{
				LL(a,b);
				a--,b--;
				if(!uf.same(a,b)){
					out("UNKNOWN");
				}
				else{
					out(uf.diff(b,a));
				}
			}
		}
	}
}
accuracy: 1
time_ms: 200
memory_kb: 6692
final_rank: 5

code_snippet: #include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
 
using namespace std;
 
template<class Abel> struct UnionFind {
    vector<int> par;
    vector<int> rank;
    vector<Abel> diff_weight;

    UnionFind(int n = 1, Abel SUM_UNITY = 0) {
        init(n, SUM_UNITY);
    }

    void init(int n = 1, Abel SUM_UNITY = 0) {
        par.resize(n); rank.resize(n); diff_weight.resize(n);
        for (int i = 0; i < n; ++i) par[i] = i, rank[i] = 0, diff_weight[i] = SUM_UNITY;
    }

    int root(int x) {
        if (par[x] == x) {
            return x;
        }
        else {
            int r = root(par[x]);
            diff_weight[x] += diff_weight[par[x]];
            return par[x] = r;
        }
    }

    Abel weight(int x) {
        root(x);
        return diff_weight[x];
    }

    bool same(int x, int y) {
        return root(x) == root(y);
    }

    bool merge(int x, int y, Abel w) {
        w += weight(x); w -= weight(y);
        x = root(x); y = root(y);
        if (x == y) return false;
        if (rank[x] < rank[y]) swap(x, y), w = -w;
        if (rank[x] == rank[y]) ++rank[x];
        par[y] = x;
        diff_weight[y] = w;
        return true;
    }

    Abel diff(int x, int y) {
        return weight(y) - weight(x);
    }
};
  
int main(){
  while(true){
    int N,M;
    cin >> N >> M;
    if(N==0 && M==0) break;
    
    UnionFind<int> tree(N);
   
    rep(i,M){
      int L,R,D;
      char Q;
      cin >> Q;
      if(Q == '!'){
        //mergeする
        cin >> L >> R >> D;
        L--; R--;
        tree.merge(L,R,D);
      }
      else{
        //LとRが同じグループなら距離が分かる
        cin >> L >> R;
        L--; R--;
        if(tree.same(L,R)){
          cout << tree.diff(L,R) << endl;
        }
        else{
          cout << "UNKNOWN" << endl;
        }
      }
    }
  }
}
accuracy: 1
time_ms: 1210
memory_kb: 3940
final_rank: 17