fn read() -> Vec { let mut s = String::new(); std::io::stdin().read_line(&mut s).unwrap(); s.trim().split_whitespace().map(|s| s.parse().unwrap()).collect() } fn run() { let n = read()[0] as usize; let mut cnt = 0; let mut query = |x: usize, y: usize| -> (i32, i32) { assert!(x != y); assert!(x < n * n - n); assert!(y < n * n - n); assert!(cnt < n * (n - 1) / 2 * 3); cnt += 1; println!("? {} {}", x + 1, y + 1); let a = read(); (a[0], a[1]) }; let m = n as i32; let hash = |x: i32, y: i32| -> (i32, i32) { assert!(m <= x && x < m * m); assert!(m <= y && y < m * m); let mut p = x / m - y / m; let mut q = x % m - y % m; if p > q { std::mem::swap(&mut p, &mut q); } (p, q) }; let mut memo = vec![(0, 0); n * n - n]; for i in 1..(n * n - n) { memo[i] = query(0, i); } let mut key = memo.clone(); key.remove(0); key.sort(); let mut ans = vec![0; n * n - n]; for i in m..(m * m) { let mut a = vec![]; for j in m..(m * m) { if i == j { continue; } a.push(hash(i, j)); } a.sort(); if a == key { assert!(ans[0] == 0); ans[0] = i; } } let mut map = std::collections::BTreeMap::new(); for i in 1..(n * n - n) { map.entry(memo[i]).or_insert(vec![]).push(i); } for i in 1..(n * n - n) { if ans[i] != 0 { continue; } let index = map.get(&memo[i]).unwrap(); assert!(index.len() <= 2); if index.len() == 1 { for v in m..(m * m) { if v == ans[0] { continue; } if hash(ans[0], v) == memo[i] { ans[i] = v; break; } } } else { let mut cond = vec![]; for v in m..(m * m) { if v == ans[0] { continue; } if hash(ans[0], v) == memo[i] { cond.push(v); } } let p = query(index[0], index[1]); if p == hash(cond[0], cond[1]) { ans[index[0]] = cond[0]; ans[index[1]] = cond[1]; } else { assert!(p == hash(cond[1], cond[0])); ans[index[0]] = cond[1]; ans[index[1]] = cond[0]; } } } assert!(ans.iter().all(|a| m <= *a && *a < m * m)); let mut p = ans.clone(); p.sort(); p.dedup(); assert!(p.len() == n * n - n); print!("!"); for a in ans { print!(" {}", a); } println!(); } fn main() { run(); }