// QCFium 法 #pragma GCC target("avx2") #pragma GCC optimize("O3") #pragma GCC optimize("unroll-loops") #ifndef HIDDEN_IN_VS // 折りたたみ用 // 警告の抑制 #define _CRT_SECURE_NO_WARNINGS // ライブラリの読み込み #include using namespace std; // 型名の短縮 using ll = long long; using ull = unsigned long long; // -2^63 ~ 2^63 = 9e18(int は -2^31 ~ 2^31 = 2e9) using pii = pair; using pll = pair; using pil = pair; using pli = pair; using vi = vector; using vvi = vector; using vvvi = vector; using vvvvi = vector; using vl = vector; using vvl = vector; using vvvl = vector; using vvvvl = vector; using vb = vector; using vvb = vector; using vvvb = vector; using vc = vector; using vvc = vector; using vvvc = vector; using vd = vector; using vvd = vector; using vvvd = vector; template using priority_queue_rev = priority_queue, greater>; using Graph = vvi; // 定数の定義 const double PI = acos(-1); int DX[4] = { 1, 0, -1, 0 }; // 4 近傍(下,右,上,左) int DY[4] = { 0, 1, 0, -1 }; int INF = 1001001001; ll INFL = 4004004003094073385LL; // (int)INFL = INF, (int)(-INFL) = -INF; // 入出力高速化 struct fast_io { fast_io() { cin.tie(nullptr); ios::sync_with_stdio(false); cout << fixed << setprecision(18); } } fastIOtmp; // 汎用マクロの定義 #define all(a) (a).begin(), (a).end() #define sz(x) ((int)(x).size()) #define lbpos(a, x) (int)distance((a).begin(), std::lower_bound(all(a), (x))) #define ubpos(a, x) (int)distance((a).begin(), std::upper_bound(all(a), (x))) #define Yes(b) {cout << ((b) ? "Yes\n" : "No\n");} #define rep(i, n) for(int i = 0, i##_len = int(n); i < i##_len; ++i) // 0 から n-1 まで昇順 #define repi(i, s, t) for(int i = int(s), i##_end = int(t); i <= i##_end; ++i) // s から t まで昇順 #define repir(i, s, t) for(int i = int(s), i##_end = int(t); i >= i##_end; --i) // s から t まで降順 #define repe(v, a) for(const auto& v : (a)) // a の全要素(変更不可能) #define repea(v, a) for(auto& v : (a)) // a の全要素(変更可能) #define repb(set, d) for(int set = 0, set##_ub = 1 << int(d); set < set##_ub; ++set) // d ビット全探索(昇順) #define repis(i, set) for(int i = lsb(set), bset##i = set; i < 32; bset##i -= 1 << i, i = lsb(bset##i)) // set の全要素(昇順) #define repp(a) sort(all(a)); for(bool a##_perm = true; a##_perm; a##_perm = next_permutation(all(a))) // a の順列全て(昇順) #define uniq(a) {sort(all(a)); (a).erase(unique(all(a)), (a).end());} // 重複除去 #define EXIT(a) {cout << (a) << endl; exit(0);} // 強制終了 #define inQ(x, y, u, l, d, r) ((u) <= (x) && (l) <= (y) && (x) < (d) && (y) < (r)) // 半開矩形内判定 // 汎用関数の定義 template inline ll powi(T n, int k) { ll v = 1; rep(i, k) v *= n; return v; } template inline bool chmax(T& M, const T& x) { if (M < x) { M = x; return true; } return false; } // 最大値を更新(更新されたら true を返す) template inline bool chmin(T& m, const T& x) { if (m > x) { m = x; return true; } return false; } // 最小値を更新(更新されたら true を返す) template inline T getb(T set, int i) { return (set >> i) & T(1); } template inline T smod(T n, T m) { n %= m; if (n < 0) n += m; return n; } // 非負mod // 演算子オーバーロード template inline istream& operator>>(istream& is, pair& p) { is >> p.first >> p.second; return is; } template inline istream& operator>>(istream& is, vector& v) { repea(x, v) is >> x; return is; } template inline vector& operator--(vector& v) { repea(x, v) --x; return v; } template inline vector& operator++(vector& v) { repea(x, v) ++x; return v; } #endif // 折りたたみ用 #if __has_include() #include using namespace atcoder; #ifdef _MSC_VER #include "localACL.hpp" #endif using mint = modint998244353; //using mint = static_modint<1000000007>; //using mint = modint; // mint::set_mod(m); namespace atcoder { inline istream& operator>>(istream& is, mint& x) { ll x_; is >> x_; x = x_; return is; } inline ostream& operator<<(ostream& os, const mint& x) { os << x.val(); return os; } } using vm = vector; using vvm = vector; using vvvm = vector; using vvvvm = vector; using pim = pair; #endif #ifdef _MSC_VER // 手元環境(Visual Studio) #include "local.hpp" #else // 提出用(gcc) inline int popcount(int n) { return __builtin_popcount(n); } inline int popcount(ll n) { return __builtin_popcountll(n); } inline int lsb(int n) { return n != 0 ? __builtin_ctz(n) : 32; } inline int lsb(ll n) { return n != 0 ? __builtin_ctzll(n) : 64; } inline int msb(int n) { return n != 0 ? (31 - __builtin_clz(n)) : -1; } inline int msb(ll n) { return n != 0 ? (63 - __builtin_clzll(n)) : -1; } #define dump(...) #define dumpel(...) #define dump_list(v) #define dump_mat(v) #define input_from_file(f) #define output_to_file(f) #define Assert(b) { if (!(b)) { vc MLE(1<<30); EXIT(MLE.back()); } } // RE の代わりに MLE を出す #endif //【Link-Cut Tree(可換モノイド)】 /* * Link_cut_tree(int n) : O(n) * 値 o() をもった n 頂点で初期化する. * 要素は可換モノイド (S, op, o) の元とする. * * Link_cut_tree(vS a) : O(n log n) * 値 a[0..n) をもった n 頂点で初期化する. * * cut(int i) : ならし O(log n) * 頂点 i とその親との間の辺を切断する. * 制約:i は根でない. * * cut(int s, int t) : ならし O(log n) * 辺 s-t を切断する(辺がなければ何もしない) * * link(int s, int t) : ならし O(log n) * 頂点 s と根 t に対し,親から子への辺 s→t を繋ぐ. * 制約:t は根である. * * set_root(int rt) : ならし O(log n) * 頂点 rt を根にする. * * S sum(int i) : ならし O(log n) * 根から頂点 i まで(両端含む)の頂点の値の総和を返す. * * set(int i, S x) : ならし O(log n) * 頂点 i の値を x に変更する. * * add(int i, S x) : ならし O(log n) * 頂点 i の値に x を加える. * * bool connectedQ(int s, int t) : ならし O(log n) * 頂点 s, t が連結かを返す. */ template class Link_cut_tree { // 参考 : https://www.slideshare.net/iwiwi/2-12188845 // 参考 : https://ei1333.github.io/library/structure/lct/link-cut-tree.hpp.html // splay 木(平衡二分探索木)のノード // key は Link-Cut 木のノードの深さとし,左ほど浅く右ほど深いものとする. struct Node { S val; // 頂点の値 S acc; // 部分木のノードの総和 bool rev; // 部分木が反転されているか Node* p; // 親へのポインタ(heavy path 内部または外部) Node* l, * r; // 左右の子へのポインタ Node(S val = o()) : val(val), acc(val), rev(false), p(nullptr), l(nullptr), r(nullptr) {} // 自身が根かを返す. bool rootQ() { // 親が設定されていなければもちろん根である. // そうでなくてもそれが heavy path 外部の親であれば自身は根である. return !p || (p->l != this && p->r != this); } // 自身の情報を子の情報をもとにして更新する. void pushup() { acc = op(l ? l->acc : o(), op(val, r ? r->acc : o())); } void pushdown() { // 反転フラグが立っていたら実際に反転しフラグを折る. if (rev) { rev = false; swap(l, r); // 自分の子については反転フラグを flip しておくだけにする. if (l) l->rev = !l->rev; if (r) r->rev = !r->rev; } // 自身の情報(acc)を子の情報をもとにして更新する. pushup(); } // 右回転する. void rotR() { Node* my_p = p, * my_pp = my_p->p; // 自分の親と自分の右の子(あれば)を繋ぐ. if (my_p->l = r) r->p = my_p; my_p->pushup(); // 自身と自分の親の親子関係を逆転させる. r = my_p; my_p->p = this; pushup(); // 自身と自分の親の親(あれば)を繋ぐ. if (p = my_pp) { if (my_pp->l == my_p) my_pp->l = this; if (my_pp->r == my_p) my_pp->r = this; } } // 左回転する. void rotL() { Node* my_p = p, * my_pp = my_p->p; // 自分の親と自分の左の子(あれば)を繋ぐ. if (my_p->r = l) l->p = my_p; my_p->pushup(); // 自身と自分の親の親子関係を逆転させる. l = my_p; my_p->p = this; pushup(); // 自身と自分の親の親(あれば)を繋ぐ. if (p = my_pp) { if (my_pp->l == my_p) my_pp->l = this; if (my_pp->r == my_p) my_pp->r = this; } } // 自身を heavy path 内の根に持ってくる(スプレー操作) void splay() { pushdown(); // 自身が根でないかぎり操作を続ける. while (!rootQ()) { Node* my_p = p; // 自分の親が根である場合 if (my_p->rootQ()) { my_p->pushdown(); pushdown(); // 適切な回転操作を行う. if (my_p->l == this) rotR(); else rotL(); } // 自分の親が根でない場合 else { Node* my_pp = my_p->p; my_pp->pushdown(); my_p->pushdown(); pushdown(); // 自分の親の親から自分まで同方向に枝が伸びている場合は zig-zig ステップ, // さもなくば zig-zag ステップを実行する. if (my_pp->l == my_p) { // zig-zig ステップ if (my_p->l == this) my_p->rotR(), rotR(); // zig-zag ステップ else rotL(), rotR(); } else { // zig-zig ステップ if (my_p->r == this) my_p->rotL(), rotL(); // zig-zag ステップ else rotR(), rotL(); } } } } }; int n; // 頂点数 vector vs; // 頂点のリスト // 木の根から頂点 v までを 1 つの heavy path にし,v を heavy path 内の根とする. void expose(Node* v) { auto v0(v); // rt : 作成途中の heavy path の根 Node* rt = nullptr; while (v) { // v をいまの heavy path 内の根にもってくる. v->splay(); // v の右の子を切り離し,作成途中の heavy path を繋ぐ. v->r = rt; rt = v; v->pushup(); // 1 つ上の heavy path に移動する. v = v->p; } // v を構築した heavy path 内の根とする. v0->splay(); } // 頂点 v とその親との間の辺を切断する. void cut(Node* v) { // 木の根から v までの heavy path を繋ぐ. expose(v); // v の親は v より 1 つ浅いので v の左の子孫になっている. Node* l = v->l; // v とその親の間の辺を切断する. v->l = nullptr; l->p = nullptr; v->pushup(); } // 頂点 v, vp を繋ぎ,v の親を vp とする(v は根であること) void link(Node* v, Node* vp) { // それぞれの木の根から v, vp までの heavy path を繋ぐ. expose(v); expose(vp); // vp と v の間の辺を繋ぐ(v の方が深いので v を vp の右の子とする) v->p = vp; vp->r = v; vp->pushup(); } // 頂点 v を木の根にする. void evert(Node* v) { expose(v); v->rev = !v->rev; } public: // 値 o() をもった n 頂点で初期化する. Link_cut_tree(int n) : n(n), vs(n) { } // 値 a[0..n) をもった n 頂点で初期化する. Link_cut_tree(const vector& a) : n(sz(a)), vs(n) { // verify : https://judge.yosupo.jp/problem/dynamic_tree_vertex_add_path_sum rep(i, n) vs[i].val = vs[i].acc = a[i]; } // 頂点 i とその親との間の辺を切断する. void cut(int i) { cut(&vs[i]); } // 辺 s-t を切断する(なければ何もしない) void cut(int s, int t) { // verify : https://judge.yosupo.jp/problem/dynamic_tree_vertex_add_path_sum if (s == t) return; expose(&vs[s]), expose(&vs[t]); if (!vs[s].p) return; if (vs[t].l == &vs[s]) { Node* l = vs[t].l; vs[t].l = nullptr; l->p = nullptr; vs[t].pushup(); } else { expose(&vs[s]); Node* l = vs[s].l; vs[s].l = nullptr; l->p = nullptr; vs[s].pushup(); } } // 頂点 s と根 t に対し,親から子への辺 s→t を繋ぐ. void link(int s, int t) { // verify : https://judge.yosupo.jp/problem/dynamic_tree_vertex_add_path_sum link(&vs[t], &vs[s]); } // 頂点 rt を根にする. void set_root(int rt) { // verify : https://judge.yosupo.jp/problem/dynamic_tree_vertex_add_path_sum evert(&vs[rt]); } // 根から頂点 i まで(両端含む)の頂点の値の総和を返す. S sum(int i) { // verify : https://judge.yosupo.jp/problem/dynamic_tree_vertex_add_path_sum expose(&vs[i]); return vs[i].acc; } // 頂点 i の値を x に変更する. void set(int i, S x) { expose(&vs[i]); vs[i].val = x; vs[i].pushup(); } // 頂点 i の値に x を加える. void add(int i, S x) { // verify : https://judge.yosupo.jp/problem/dynamic_tree_vertex_add_path_sum expose(&vs[i]); vs[i].val = op(x, vs[i].val); vs[i].pushup(); } // 頂点 s, t が連結かを返す. bool connectedQ(int s, int t) { // verify : https://atcoder.jp/contests/abc350/tasks/abc350_g if (s == t) return true; expose(&vs[s]), expose(&vs[t]); return vs[s].p; } #ifdef _MSC_VER friend ostream& operator<<(ostream& os, Link_cut_tree LCT) { // verify : https://atcoder.jp/contests/abc355/tasks/abc355_f rep(i, LCT.n) { os << LCT.vs[i].val << " "; } return os; } #endif }; //【総和 可換モノイド】 /* verify : https://atcoder.jp/contests/arc035/tasks/arc035_d */ using S001 = int; S001 op001(S001 a, S001 b) { return a + b; } S001 e001() { return 0; } #define Sum_monoid S001, op001, e001 bool check(int n, vvi p, vi res) { dsu d(n); rep(j, n) { if (res[j] == -1) continue; if (d.same(j, p[res[j]][j])) return false; d.merge(j, p[res[j]][j]); } return true; } int main() { // input_from_file("input.txt"); // output_to_file("output.txt"); int n, K; cin >> n >> K; vvi p(n - 1, vi(n)); cin >> p; --p; Link_cut_tree g(n); mt19937_64 mt(0); uniform_int_distribution rnd(0, INF); vi res(n, -1); vi res_inv(n, -1); vi q(n - 1); iota(all(q), 0); vi rem; rep(i, n) rem.push_back(i); while (!q.empty()) { dump("-----------------------------------"); dump(res); dump(res_inv); dump(q); int W = sz(q); int w = rnd(mt) % W; int i = q[w]; if (w != W - 1) swap(q[w], q[W - 1]); q.pop_back(); shuffle(all(rem), mt); int L = sz(rem); dump(rem); bool ok = false; rep(l, L) { int j = rem[l]; if (p[i][j] == -1) { continue; } if (g.connectedQ(p[i][j], j)) continue; g.set_root(j); g.link(p[i][j], j); res[j] = i; res_inv[i] = j; if (l != L - 1) swap(rem[l], rem[L - 1]); rem.pop_back(); ok = true; break; } if (ok) continue; int l = rnd(mt) % L; int j = rem[l]; if (p[i][j] == -1) { q.push_back(i); continue; } int j2 = p[i][j]; int i2 = res[j2]; g.cut(p[i2][j2], j2); rem[l] = j2; res[j2] = -1; res_inv[i2] = -1; q.push_back(i2); g.set_root(j); g.link(p[i][j], j); res[j] = i; res_inv[i] = j; } dump("-----------------------------------"); dump(res); dump(res_inv); dump(q); dump(rem); dump("OK?:", check(n, p, res)); Yes(1); rep(i, n) cout << res[i] + 1 << " \n"[i == n - 1]; }