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 len = n * n - n; let mut cnt = 0; let mut query = |x: usize, y: usize| -> (i32, i32) { assert!(x != y); assert!(x < len); assert!(y < len); 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); len]; let mut used = vec![]; for i in 1..len { memo[i] = query(0, i); used.push((0, i, memo[i])); } let mut key = memo.clone(); key.remove(0); key.sort(); let mut ans = vec![0; len]; 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 (&memo, index) in map.iter() { assert!(index.len() <= 2); if index.len() == 1 { let i = index[0]; for v in m..(m * m) { if v == ans[0] { continue; } if hash(ans[0], v) == memo { ans[i] = v; break; } } } else { let (i, j) = (index[0], index[1]); let mut cond = vec![]; for v in m..(m * m) { if v == ans[0] { continue; } if hash(ans[0], v) == memo { cond.push(v); } } assert!(cond.len() == 2); let (a, b) = hash(cond[0], cond[1]); assert!(a != b); let p = query(i, j); used.push((i, j, p)); let (x, y) = (cond[0], cond[1]); if p == hash(x, y) { ans[i] = x; ans[j] = y; } else { ans[i] = y; ans[j] = x; } } } { let mut p = ans.clone(); p.sort(); p.dedup(); assert!(p.len() == len); assert!(p[0] == m); } print!("!"); for a in ans { print!(" {}", a); } println!(); } fn main() { run(); }