image.png

문제 태그

아이디어

정답

import sys
sys.setrecursionlimit(10**6)

class Vertex:
    def __init__(self, id_val=0, weight_val=0):
        self.id = id_val
        self.weight = weight_val
        self.handle = None

class Cluster:
    def __init__(self, v=0, ws=0, sdl=0, sdr=0):
        self.value = v
        self.weight_sum = ws
        self.sum_dist_left = sdl
        self.sum_dist_right = sdr

    def toggle(self):
        self.sum_dist_left, self.sum_dist_right = self.sum_dist_right, self.sum_dist_left

    @staticmethod
    def compress(L, mid_vertex, R):
        mid_w = mid_vertex.weight
        L_val = L.value
        R_val = R.value
        L_ws = L.weight_sum
        R_ws = R.weight_sum
        return Cluster(
            L_val + R_val,
            L_ws + R_ws - mid_w,
            L.sum_dist_left + R.sum_dist_left + L_val * (R_ws - mid_w),
            L.sum_dist_right + R.sum_dist_right + R_val * (L_ws - mid_w)
        )

    @staticmethod
    def rake(path_part, shared_vertex, side_subtree):
        shared_w = shared_vertex.weight
        path_val = path_part.value
        side_ws = side_subtree.weight_sum
        return Cluster(
            path_val,
            path_part.weight_sum + side_ws - shared_w,
            path_part.sum_dist_left + side_subtree.sum_dist_right + path_val * (side_ws - shared_w),
            path_part.sum_dist_right + side_subtree.sum_dist_right
        )

class Type:
    COMPRESS = 0
    RAKE = 1
    EDGE = 2

class Node:
    def __init__(self):
        self.vs = [None, None]
        self.dat = Cluster()
        self.p = None
        self.q = None
        self.ch = [None, None]
        self.type = None
        self.rev = False
        self.guard = False

class TopTree:
    node_pool = [Node() for _ in range(2000000)]
    pool_idx = 0

    def __init__(self):
        self.id_cluster = Cluster(0)

    def alloc_node(self):
        if self.pool_idx >= 2000000:
            print("Node pool overflow!", file=sys.stderr)
            sys.exit(1)
        node = self.node_pool[self.pool_idx]
        self.pool_idx += 1
        return node

    def parent_dir(self, t):
        if not t or not t.p or t.p.guard:
            return -1
        p = t.p
        if p.ch[0] == t:
            return 0
        elif p.ch[1] == t:
            return 1
        return -1

    def parent_dir_ignore_guard(self, t):
        if not t or not t.p:
            return -1
        p = t.p
        if p.ch[0] == t:
            return 0
        elif p.ch[1] == t:
            return 1
        return -1

    def toggle(self, t):
        if t.type == Type.EDGE:
            t.vs[0], t.vs[1] = t.vs[1], t.vs[0]
            t.dat.toggle()
        elif t.type == Type.COMPRESS:
            t.vs[0], t.vs[1] = t.vs[1], t.vs[0]
            t.dat.toggle()
            t.rev = not t.rev

    def propagate(self, t):
        if not t or t.type != Type.COMPRESS or not t.rev:
            return
        l = t.ch[0]
        r = t.ch[1]
        t.ch[0], t.ch[1] = t.ch[1], t.ch[0]
        self.toggle(l)
        self.toggle(r)
        t.rev = False

    def pushup(self, t):
        if not t:
            return None
        self.propagate(t)
        l = t.ch[0]
        r = t.ch[1]

        if t.type == Type.EDGE:
            value = t.dat.value
            u = t.vs[0]
            v = t.vs[1]
            u_w = u.weight
            v_w = v.weight
            t.dat = Cluster(value, u_w + v_w, v_w * value, u_w * value)

        if t.type == Type.COMPRESS:
            self.propagate(l)
            self.propagate(r)
            t.vs[0] = l.vs[0]
            t.vs[1] = r.vs[1]
            lf = l.dat
            if t.q:
                self.propagate(t.q)
                lf = Cluster.rake(l.dat, l.vs[1], t.q.dat)
            t.dat = Cluster.compress(lf, r.vs[0], r.dat)
            l.vs[1].handle = t
        elif t.type == Type.RAKE:
            t.vs[0] = l.vs[0]
            t.vs[1] = l.vs[1]
            t.dat = Cluster.rake(l.dat, l.vs[1], r.dat)

        if t.type != Type.RAKE:
            if not t.p:
                t.vs[0].handle = t
                t.vs[1].handle = t
            elif t.p.type == Type.COMPRESS and self.parent_dir(t) == -1:
                t.vs[0].handle = t
            elif t.p.type == Type.RAKE:
                t.vs[0].handle = t
        return t

    def set_toggle(self, v):
        self.toggle(v)
        self.propagate(v)

    def pushdown(self, t):
        if not t:
            return
        path = []
        curr = t
        while curr:
            path.append(curr)
            curr = curr.p
        for node in reversed(path):
            self.propagate(node)

    def rotate(self, t, x, dir):
        y = x.p
        par = self.parent_dir_ignore_guard(x)

        x.ch[dir ^ 1] = t.ch[dir]
        if t.ch[dir]:
            t.ch[dir].p = x

        t.ch[dir] = x
        x.p = t
        t.p = y

        if par != -1:
            if y:
                y.ch[par] = t
        elif y and y.type == Type.COMPRESS:
            y.q = t

        self.pushup(x)
        self.pushup(t)
        if y and not y.guard:
            self.pushup(y)

    def splay(self, t):
        self.pushdown(t)
        while self.parent_dir(t) != -1:
            q = t.p
            if q.type != t.type:
                break
            if self.parent_dir(q) != -1 and q.p and q.p.type == q.type:
                r = q.p
                qt_dir = self.parent_dir(t)
                rq_dir = self.parent_dir(q)
                if rq_dir == qt_dir:
                    self.rotate(q, r, rq_dir ^ 1)
                    self.rotate(t, q, qt_dir ^ 1)
                else:
                    self.rotate(t, q, qt_dir ^ 1)
                    self.rotate(t, r, rq_dir ^ 1)
            else:
                qt_dir = self.parent_dir(t)
                self.rotate(t, q, qt_dir ^ 1)

    def expose(self, t):
        self.pushdown(t)
        while True:
            if t.type == Type.COMPRESS:
                self.splay(t)
            p = t.p
            if not p:
                break
            n = None
            if p.type == Type.RAKE:
                self.propagate(p)
                self.splay(p)
                n = p.p
            elif p.type == Type.COMPRESS:
                self.propagate(p)
                if p.guard and self.parent_dir_ignore_guard(t) != -1:
                    break
                n = p
            if not n:
                break
            self.splay(n)
            dir = self.parent_dir_ignore_guard(n)
            if dir == -1 or (n.p and n.p.type == Type.RAKE):
                dir = 0
            c = n.ch[dir]
            if dir == 1:
                self.set_toggle(c)
                self.set_toggle(t)
            n_dir = self.parent_dir(t)
            if n_dir != -1:
                r_node = t.p
                self.propagate(c)
                self.propagate(r_node)
                r_node.ch[n_dir] = c
                if c:
                    c.p = r_node
                n.ch[dir] = t
                t.p = n
                self.pushup(c)
                self.pushup(r_node)
                self.pushup(t)
                self.pushup(n)
                self.splay(r_node)
            else:
                self.propagate(c)
                n.q = c
                if c:
                    c.p = n
                n.ch[dir] = t
                t.p = n
                self.pushup(c)
                self.pushup(t)
                self.pushup(n)
            if t.type == Type.EDGE:
                t = n
        return t

    def recursive_pushup(self, t):
        if not t:
            return
        self.propagate(t)
        self.recursive_pushup(t.ch[0])
        self.recursive_pushup(t.ch[1])
        if t.type == Type.COMPRESS:
            self.recursive_pushup(t.q)
        self.pushup(t)

    def find_median_in_cluster(self, t, ext_W0, ext_W1):
        if not t:
            return None
        total_W = t.dat.weight_sum + ext_W0 + ext_W1
        half = total_W >> 1
        self.pushdown(t)

        if t.type == Type.EDGE:
            v0 = t.vs[0]
            v1 = t.vs[1]
            W_dir0 = ext_W0
            W_dir1 = v1.weight + ext_W1
            if max(W_dir0, W_dir1) <= half:
                return v0
            elif W_dir1 > half:
                return v1
            else:
                return v0
        elif t.type == Type.COMPRESS:
            l = t.ch[0]
            r = t.ch[1]
            q = t.q
            mid = r.vs[0]
            mid_w = mid.weight
            W_left = l.dat.weight_sum - mid_w + ext_W0
            W_right = r.dat.weight_sum - mid_w + ext_W1
            W_side = q.dat.weight_sum - mid_w if q else 0

            max_w = max(W_left, W_right, W_side)
            if max_w <= half:
                return mid
            if W_left == max_w:
                return self.find_median_in_cluster(l, ext_W0, W_right + W_side)
            elif W_right == max_w:
                return self.find_median_in_cluster(r, W_left + W_side, ext_W1)
            else:
                return self.find_median_in_cluster(q, 0, W_left + W_right)
        elif t.type == Type.RAKE:
            l = t.ch[0]
            r = t.ch[1]
            mid = t.vs[1]
            mid_w = mid.weight
            W_left = l.dat.weight_sum - mid_w + ext_W0
            W_side = r.dat.weight_sum - mid_w
            max_w = max(W_left, W_side, ext_W1)
            if max_w <= half:
                return mid
            if W_left == max_w:
                return self.find_median_in_cluster(l, ext_W0, W_side + ext_W1)
            elif W_side == max_w:
                return self.find_median_in_cluster(r, 0, W_left + ext_W1)
            else:
                return mid
        return None

    def create(self, id_val=0, weight=0):
        t = Vertex(id_val, weight)
        dummy = Vertex(0, 0)
        self.link(t, self.id_cluster, dummy)
        return t

    def edge(self, u, w, v):
        t = self.alloc_node()
        t.vs[0] = u
        t.vs[1] = v
        u_w = u.weight
        v_w = v.weight
        t.dat = Cluster(w.value, u_w + v_w, v_w * w.value, u_w * w.value)
        t.type = Type.EDGE
        return self.pushup(t)

    def compress(self, l, r):
        t = self.alloc_node()
        t.ch[0] = l
        t.ch[1] = r
        t.type = Type.COMPRESS
        l.p = t
        r.p = t
        return self.pushup(t)

    def rake(self, l, r):
        t = self.alloc_node()
        t.ch[0] = l
        t.ch[1] = r
        t.type = Type.RAKE
        l.p = t
        r.p = t
        return self.pushup(t)

    def expose_vertex(self, v):
        return self.expose(v.handle)

    def soft_expose(self, u, v):
        self.pushdown(u.handle)
        self.pushdown(v.handle)
        rt = self.expose(u.handle)

        if u.handle == v.handle:
            if rt.vs[1] == u or rt.vs[0] == v:
                self.set_toggle(rt)
            return

        rt.guard = True
        soft = self.expose(v.handle)
        rt.guard = False

        self.pushup(rt)
        if self.parent_dir(soft) == 0:
            self.set_toggle(rt)

    def bring(self, rt):
        rk = rt.q
        if not rk:
            ll = rt.ch[0]
            if ll:
                ll.p = None
            self.pushup(ll)
        elif rk.type == Type.COMPRESS or rk.type == Type.EDGE:
            nr = rk
            self.set_toggle(nr)
            rt.ch[1] = nr
            nr.p = rt
            rt.q = None
            self.pushup(nr)
            self.pushup(rt)
        elif rk.type == Type.RAKE:
            self.propagate(rk)
            while rk.ch[1] and rk.ch[1].type == Type.RAKE:
                self.propagate(rk.ch[1])
                rk = rk.ch[1]

            self.pushdown(rk)
            rt.guard = True
            self.splay(rk)
            rt.guard = False

            ll = rk.ch[0]
            rr = rk.ch[1]
            self.propagate(ll)
            self.set_toggle(rr)

            rt.ch[1] = rr
            rr.p = rt
            rt.q = ll
            if ll:
                ll.p = rt

            self.pushup(ll)
            self.pushup(rr)
            self.pushup(rt)

    def link(self, u, w, v):
        if u.id > v.id:
            u, v = v, u
        nnu = u.handle
        nnv = v.handle
        if not nnu and not nnv:
            return self.edge(u, w, v)

        ee = self.edge(u, w, v)
        ll = None

        if nnv:
            vv = self.expose(nnv)
            self.propagate(vv)
            if vv.vs[1] == v:
                self.set_toggle(vv)

            if vv.vs[0] == v:
                ll = self.compress(ee, vv)
            else:
                nv = vv
                self.propagate(nv)
                ch = nv.ch[0]
                self.propagate(ch)
                nv.ch[0] = ee
                ee.p = nv
                self.pushup(ee)

                bt = nv.q
                rk = self.rake(bt, ch) if bt else ch
                if bt:
                    bt.p = rk
                    ch.p = rk
                    self.pushup(bt)
                    self.pushup(ch)

                nv.q = rk
                if rk:
                    rk.p = nv
                self.pushup(rk)
                self.pushup(nv)
                ll = nv
        else:
            ll = ee

        if nnu:
            uu = self.expose(nnu)
            self.propagate(uu)
            if uu.vs[0] == u:
                self.set_toggle(uu)

            if uu.vs[1] == u:
                tp = self.compress(uu, ll)
                self.pushup(tp)
            else:
                nu = uu
                self.propagate(nu)
                ch = nu.ch[1]
                if ch:
                    self.toggle(ch)
                    self.propagate(ch)

                nu.ch[1] = ll
                ll.p = nu
                self.pushup(ll)

                al = nu.q
                rk = self.rake(al, ch) if al else ch
                if al:
                    al.p = rk
                    if ch:
                        ch.p = rk
                    self.pushup(al)
                    if ch:
                        self.pushup(ch)

                nu.q = rk
                if rk:
                    rk.p = nu
                self.pushup(rk)
                self.pushup(nu)
        return ee

    def cut(self, u, v):
        self.soft_expose(u, v)
        rt = u.handle
        self.propagate(rt)

        rr = rt.ch[1]
        rr.p = None
        self.set_toggle(rr)

        self.bring(rr)
        self.bring(rt)

    def path(self, u, v):
        self.soft_expose(u, v)
        rt = u.handle
        self.propagate(rt)
        if rt.ch[1]:
            self.propagate(rt.ch[1])
            if rt.ch[1].ch[0]:
                return rt.ch[1].ch[0]
        return None

    def get_path(self, u, v):
        p = self.path(u, v)
        return p.dat if p else Cluster()

    def find_1_median(self, v):
        self.expose(v.handle)
        rt = v.handle
        return self.find_median_in_cluster(rt, 0, 0)

    def get_1_median_value(self, v):
        w = self.find_1_median(v)
        rt = self.expose(w.handle)
        self.propagate(rt)
        if w == rt.vs[0] or w == rt.vs[1]:
            if rt.vs[0] != w:
                self.set_toggle(rt)
            return rt.dat.sum_dist_left
        else:
            sub = w.handle
            self.splay(sub)
            left = sub.ch[0]
            right = sub.ch[1]
            q = sub.q
            sum_left = left.dat.sum_dist_right if left else 0
            sum_right = right.dat.sum_dist_left if right else 0
            sum_side = q.dat.sum_dist_right if q else 0
            return sum_left + sum_right + sum_side

    def toggle_weight(self, v):
        v.weight = 1 - v.weight
        self.recursive_pushup(v.handle)
        t = v.handle.p
        while t:
            self.pushup(t)
            t = t.p

N, Q = map(int, input().split())
tt = TopTree()
vertices = [None] + [tt.create(i, weight=1) for i in range(1, N + 1)]
prev = 0
result = []
for _ in range(Q):
    k, *args = map(int, input().split())
    if k == 1:
        a, b, c = args

        a = (a - 1 + prev) % N + 1
        b = (b - 1 + prev) % N + 1
        # print("link", a, b, c)
        tt.link(vertices[a], Cluster(c), vertices[b])
    elif k == 2:
        a, b = args

        a = (a - 1 + prev) % N + 1
        b = (b - 1 + prev) % N + 1
        # print("cut", a, b)
        tt.cut(vertices[a], vertices[b])
    elif k == 3:
        a = args[0]
        a = (a - 1 + prev) % N + 1
        # print("query",a)
        tt.toggle_weight(vertices[a])
        S = tt.get_1_median_value(vertices[a])
        prev = (prev + S) % N
        result.append(S)
print("\\n".join(map(str, result)))