#ai回答5 import sys import random # Pythonの再帰や深いスタック操作は遅いため、極力iterativeに書く # I/Oの高速化用ショートカット input = sys.stdin.readline write = sys.stdout.write flush = sys.stdout.flush def solve(): # Nの読み込み try: line = input() while line and not line.strip(): # 空行スキップ line = input() if not line: return N = int(line.strip()) except ValueError: return # --- クエリ関数 (インライン化に近い形で高速化) --- def query(i, j): write(f"1 {i} {j}\n") flush() try: return int(input()) except ValueError: return -1 def answer(p_list): # p_listを展開して文字列化するコストを一回にまとめる write(f"2 {' '.join(map(str, p_list))}\n") flush() indices = list(range(1, N + 1)) # ------------------------------------------------------- # Phase 1: Pivot探索 (最速脱出) # ------------------------------------------------------- # ランダムにペアを選び、成功したら即座に採用する。 # N=5のような小規模ケースでも、確率的に数回でヒットする。 # 最大でも 2N 回程度回せばほぼ確実。 p = -1 tested = set() # ランダムシャッフルして先頭からペアを作っていく方がsampleより速い場合がある # ここでは単純なランダムピックアップを行う while True: # random.sampleは少し重いので、indicesから直接叩く u = indices[int(random.random() * N)] v_node = indices[int(random.random() * N)] if u == v_node: continue if u > v_node: u, v_node = v_node, u # 正規化 if (u, v_node) in tested: continue tested.add((u, v_node)) res = query(u, v_node) if res != -1: p = u break # 安全策: N*3回試してダメなら諦める(通常ありえない) if len(tested) > N * 3: p = indices[0] # fallback break # ------------------------------------------------------- # Phase 2: グラフ構築 # ------------------------------------------------------- # ここがボトルネックになりうるが、N-1回のクエリは必須。 # 最適化余地なし。 adj = {} in_degree = [0] * (N + 1) # dictよりlistの方がアクセスが速い # pを除外したリストを作成 targets = [x for x in indices if x != p] for i in targets: res = query(p, i) if res != -1: adj[i] = res in_degree[res] += 1 # ヘッド抽出 heads = [i for i in indices if in_degree[i] == 0] v = len(heads) # p の値 head_set = set(heads) # ------------------------------------------------------- # Phase 3: ヘッドの順序特定 # ------------------------------------------------------- head_val = {} if v == 1: head_val[heads[0]] = 1 else: # Step 3-1: チェーン長による候補絞り込み # 計算量は O(N) だがクエリ0回なので安い chain_len = {} max_len = -1 for h in heads: curr = h length = 0 while curr in adj: curr = adj[curr] length += 1 l = length + 1 chain_len[h] = l if l > max_len: max_len = l # 候補は最大長を持つヘッドのみ candidates = [h for h in heads if chain_len[h] == max_len] one_node = -1 if len(candidates) == 1: one_node = candidates[0] else: # 候補内総当たり # Phase 1 で見つけた p は比較的小さい値になりやすいため # candidates の数も少なくなる傾向にある scores = {h: 0 for h in candidates} for h1 in candidates: for h2 in candidates: res = query(h1, h2) if res in head_set: scores[h1] += 1 # 最もヒットしたのが 1 one_node = max(scores, key=scores.get) head_val[one_node] = 1 # Step 3-2: 1を使って数珠つなぎ curr = one_node unknown_heads = set(heads) unknown_heads.remove(one_node) # リスト化して少しでもループコストを下げる # query結果が head_set にあるか判定 for val in range(2, v + 1): if len(unknown_heads) == 1: # 残り1つなら確定 last = unknown_heads.pop() head_val[last] = val break # query(1, curr) -> 次の値に対応するヘッド res = query(one_node, curr) # 見つかった res は次の値を持つヘッド head_val[res] = val curr = res unknown_heads.discard(res) # ------------------------------------------------------- # Phase 4: 復元 # ------------------------------------------------------- P_res = [0] * N for h in heads: val_start = head_val[h] curr = h current_v = val_start # whileループ展開 while True: P_res[curr - 1] = current_v if curr in adj: curr = adj[curr] current_v += v else: break answer(P_res) # メイン実行部 line = input() if line: try: T_cases = int(line.strip()) for _ in range(T_cases): solve() except ValueError: pass