ICPC2023南京站题解(A C D F G I L M)
本场金牌线为7题前一半。做出8题可稳金牌,这里是难度前8题的题解。
ICPC2023南京站
I:
签到题。
#include <bits/stdc++.h>
#define ll long long
#define QWQ cout<<"QwQ"<<endl;
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
using namespace std;
const ll N=501010;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
int T;
int n,m;
struct E{
int wei,zhi;
}a[N];
inline bool cmp(E A,E B) { return A.wei<B.wei; }
int main() {
T = read();
while(T--) {
bool flag = 1;
n = read(); m = read();
for(int i=1;i<=m;i++) {
a[i].wei = read(); a[i].zhi = read();
}
sort(a+1,a+m+1,cmp);
for(int i=1;i<=m;i++) {
if(a[i].wei-a[i-1].wei==a[i].zhi-a[i-1].zhi) continue;
if(a[i].zhi<a[i].wei-a[i-1].wei) continue;
flag = 0;
break;
}
if(flag) cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
F:
等价重写,可以轻松地想到本题和图论有关,每个操作修改的数字是独一无二的,因此每个覆盖事件都可以产生一个先后关系,我们可以将某个位置先写入的数字连向后写入的数字,在拓扑排序中就可以表示先写入较早的数字。
我WA了一发哈哈,因为没考虑完整,连边只需要连向每个位置最后覆盖的数字就行,之前谁把谁覆盖没有关系。
#include <bits/stdc++.h>
#define ll long long
#define QWQ cout<<"QwQ"<<endl;
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
using namespace std;
const ll N=501010;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
int T;
int n,m;
int a[N];
int ru[N];
vector <int> e[N];
int ans[N],cnt;
priority_queue <int> q;
vector <int> g[N];
void chushihua() {
for(ll i=1;i<=n;i++) ru[i] = 0, e[i].clear();
for(ll i=1;i<=m;i++) g[i].clear();
cnt = 0;
}
int main() {
int x,y;
T = read();
while(T--) {
chushihua();
n = read(); m = read();
for(int i=1;i<=n;i++) {
x = read();
while(x--) {
y = read();
g[y].push_back(i);
}
}
for(int i=1;i<=m;i++) {
if(g[i].size()==0) continue;
int hou = g[i].back();
for(auto v : g[i]) if(v != hou) e[v].push_back(hou), ru[hou]++;
}
for(int i=1;i<=n;i++) if(!ru[i]) q.push(i);
while(!q.empty()) {
int u = q.top(); q.pop();
ans[++cnt] = u;
FOR() {
int v = e[u][i];
ru[v]--;
if(ru[v]==0) q.push(v);
}
}
bool tong = 1;
for(int i=1;i<=cnt;i++) if(i!=ans[i]) tong = 0;
if(tong) {
cout<<"No\n";
continue;
}
cout<<"Yes\n";
for(int i=1;i<=n;i++) cout<<ans[i]<<" \n"[i==n];
}
return 0;
}
C:
官方题解中给出了两种解法,都有思考价值。
解法一:
异或运算满足不等式:\(a-b\leq a\oplus b \leq a+b\),那么我们把题目中的 g 写出来:\(g=(kP+1)\oplus(P-1)\),它也满足不等式:\((k-1)P+2\leq (kP+1)\oplus(P-1)\leq (k+1)P\)。
这个不等式能告诉我们什么?虽然我们的 \((kP+1)\) 异或上了 \((P-1)\) 使得结果 g 的具体值不确定,但是 g 是有取值范围的,这个范围的大小不超过2P,随着 \(k\) 的增长,g 的取值范围左右各增加一个 P,也就是说,g 的取值范围接近上限 m 时,k 的取值就在 \(\lfloor\frac mP\rfloor\) 左右,小于这个值时,g 一定比 m 小,大于这个值时 g 一定比 m 大,在 \(\lfloor\frac mP\rfloor\) 左右时特判即可。
解法二:
这个做法是比较无脑且科技的,比较推荐:
将 [l,r] 这个区间的所有整数异或上一个值 X ,可以得到 log 段连续的区间。[0,m] 这个区间异或上 (P-1),每一段判断有多少 (kP+1) 即可。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=101010;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
ll T;
ll m,P;
struct D{
ll l,r;
}d[N];
inline bool cmp(D A,D B) { return A.l < B.l; }
ll cnt;
void get_fen(ll R,ll X) {
ll da = 1ll << 60ll;
d[cnt=1] = {0,R};
for(ll i=60;i>=0;i--,da>>=1) {
if(d[1].l+da<=d[1].r) {
if((X>>i)&1) {
d[++cnt] = {d[1].l+da,d[1].l+2*da-1};
d[1].r -= da;
}
else {
d[++cnt] = {d[1].l,d[1].l+da-1};
d[1].l += da;
}
}
else if((X>>i)&1) d[1].l += da, d[1].r += da;
}
sort(d+1,d+cnt+1,cmp);
// for(ll i=1;i<=cnt;i++) cout<<d[i].l<<" "<<d[i].r<<"\n";
}
inline ll ask(ll R) {
if(R<=0) return 0;
return (R-1)/P+1;
}
int main() {
T = read();
while(T--) {
P = read(); m = read();
get_fen(m,P-1);
ll ans = 0;
for(ll i=1;i<=cnt;i++) {
ans += ask(d[i].r);
ans -= ask(d[i].l-1);
}
cout<<ans<<"\n";
}
return 0;
}
G:
可以免费选物品的背包,总感觉哪里做过。
假如从珠宝店出来,你拿着一些物品,把这些物品分为两类,一类是你买的,一类是你免费选的,那么可以肯定的是,你免费选的那些物品,一定是价格比较高的,这样省钱。
所以这两类物品是存在一个分界线的,是按价格排序的分界线。
枚举一个价格,设免费选的都高于这个价格,买来的都低于这个价格,那就高于这个价格的优先选价值高的,低于这个价格的用背包。
前者优先队列,后者朴素背包。
#include <bits/stdc++.h>
#define ll long long
#define QWQ cout<<"QwQ"<<endl;
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
using namespace std;
const ll N=501010;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
ll n,W,K;
struct E{
ll w,v;
}a[N];
inline bool cmp(E A,E B) { return A.w > B.w; }
priority_queue <ll, vector<ll>, greater<ll> > q;
ll qian[N];
ll f[N];
int main() {
n = read(); W = read(); K = read();
for(ll i=1;i<=n;i++) {
a[i].w = read(); a[i].v = read();
}
sort(a+1,a+n+1,cmp);
ll now = 0;
for(ll i=1;i<=n;i++) {
now += a[i].v;
q.push(a[i].v);
if(i>K) now -= q.top(), q.pop();
qian[i] = now;
}
ll ans = 0;
for(ll i=n;i>=1;i--) {
ll da = 0;
for(ll j=W;j>=a[i].w;j--) {
f[j] = max(f[j], f[j-a[i].w] + a[i].v);
da = max(f[j], da);
}
ans = max(ans,da+qian[i-1]);
}
cout<<ans;
return 0;
}
A:
南京站四次重现,还挺酷的。
题目比较吓人,但是稍微想想会发现,如果我是袋鼠,我会把我能走到的地方都走一遍,如果还没有赢,那我就赢不了了。
我能走的区域就是四连通块,如果场内有能够包含我这个连通块的其他连通块,那我就成为不了赢家,反之肯定能赢,且我连通块内的其他袋鼠也一定能赢(这个很关键)。
枚举所有的连通块,扫一遍全图,每个位置判断需要花费我连通块大小的时间,设每个连通块大小为 \(siz_i\) ,总复杂度为 \(\sum siz_i*nm=(nm)^2\) ,因为题目中 \(n*m\) 是 1000,所以平方可过。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1010;
const int inf=0x3f3f3f3f;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
ll T;
int n,m;
char s[N];
int ff[] = {1,-1,0,0};
int gg[] = {0,0,1,-1};
int a[N][N];
int shang, xia, zuo, you;
int vis[N][N];
struct D { int x,y; }st[N];
int cnt;
int ans;
void chushihua() {
for(int i=0;i<=n+1;i++) for(int j=0;j<=m+1;j++) vis[i][j] = a[i][j] = 0;
ans = 0;
}
void DFS(int x,int y) {
st[++cnt] = {x,y};
vis[x][y] = 1;
shang = min(shang,x);
zuo = min(zuo,y);
xia = max(xia,x);
you = max(you,y);
for(int k=0;k<4;k++) {
int xx = ff[k] + x;
int yy = gg[k] + y;
if(vis[xx][yy] || !a[xx][yy]) continue;
DFS(xx,yy);
}
}
int main() {
T = read();
while(T--) {
chushihua();
n = read(); m = read();
for(int i=1;i<=n;i++) {
scanf("%s",s+1);
for(int j=1;j<=m;j++) {
if(s[j]=='.') a[i][j] = 1;
else a[i][j] = 0;
}
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
if(a[i][j] && !vis[i][j]) {
cnt = 0;
shang = zuo = inf;
xia = you = 0;
DFS(i,j);
int shu = xia-shang+1;
int heng = you-zuo+1;
bool you = 0;
for(int I=1;I<=n;I++) {
if(I+shu-1>n) break;
for(int J=1;J<=m;J++) {
if(J+heng-1>m) break;
if(I==shang && J==zuo) continue;
bool can = 1;
for(int k=1;k<=cnt;k++) {
if(!a[ I+st[k].x-shang ][ J+st[k].y-zuo ]) { can = 0; break; }
}
if(can) {you = 1; break;}
}
if(you==1) break;
}
if(!you) ans += cnt;
}
}
}
cout<<ans<<endl;
}
return 0;
}
L:
这题思考难度是签到,但是难写程度甚至导致成为了银牌题。
电梯运包裹,因为包裹大小只有 1 和 2,且贪心满足要求,所以是可以排完序后直接模拟的,直接模拟的话可能会有些卡手,因为想使用两个set分别存 1 和 2 的包裹的话,来回选还是比较难写的。
我的做法把所有包裹放在一起按楼层排序,如何寻找最高的大小为 1 的包裹呢,想必大家都听说过树状数组二分这个东西吧?支持单点减(某个1包裹被运完),查询第k个数的值(查询楼层最大的1包裹的位置)。
但是我觉得麻烦没用树状数组,我是直接二分的,在前缀和上二分,找楼层最大的1包裹,因为答案是单调的,所以某个包裹被运完之后,我可以把二分的右区间改成这个楼层,这样就保证答案一定还没运完。
题解提供了避免区分 1 和 2 包裹的转化做法。因为保证电梯大小是偶数,这就诱使选手去思考下更简单的转化:如果我电梯只剩下1的空间,我挑选一个1包裹,和我把下一个2包裹拆成两个1,效果是一样的,都不影响最终答案。证明不难,也比较妙。最终结论就是,2包裹全拆成1包裹就行了,不用讨论了。
但是我想练练码力所以没转化。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=101010;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
ll T;
ll n,K;
struct E{
ll num,fl,cl;
}a[N];
inline bool cmp(E A,E B) { return A.fl < B.fl; }
ll sum[N];
ll R;
void chushihua() {
R = 0;
}
int main() {
T = read();
while(T--) {
chushihua();
n = read(); K = read();
for(ll i=1;i<=n;i++) {
a[i].num = read(); a[i].cl = read(); a[i].fl = read();
}
sort(a+1,a+n+1,cmp);
for(ll i=1;i<=n;i++) {
sum[i] = sum[i-1] + (a[i].cl==1);
if(a[i].cl==1) R = i;
}
ll ans = 0;
for(ll i=n;i>=1;i--) {
// for(ll j=1;j<=n;j++) cout<<a[j].fl<<","<<a[j].cl<<","<<a[j].num<<" ";
// cout<<"ans = "<<ans<<endl;
if(a[i].cl * a[i].num >= K) ans += a[i].fl * ((a[i].cl * a[i].num) / K), a[i].num %= K / a[i].cl;
if(!a[i].num) continue;
ll now = K;
ll j = i;
ans += a[i].fl;
while(1) {
if(a[j].cl * a[j].num < now) now -= a[j].cl * a[j].num, a[j].num = 0;
else { a[j].num -= now / a[j].cl; now %= a[j].cl; break; }
j--;
if(j==0) break;
}
if(j==0) break;
R = min(j,R);
if(sum[R]==0 || now==0) continue;
ll l = 1, r = R, mid, res;
while(l<=r) {
mid = l+r >> 1;
if(sum[mid]==sum[j]) res = mid, r = mid-1;
else l = mid+1;
}
R = res;
a[R].num--;
if(!a[R].num) R--;
}
cout<<ans<<"\n";
}
return 0;
}
M:
好经典的题意,上一次见降雨存水的题好像是一道防AK几何。
好心的出题人怕大家做不出来,温馨地给了一个“更正式地”,其实是个关键的题意转化:\(ans = \sum\limits_{i=1}^n (min(f_i,g_i)-a_i)\),\(f_i,g_i\) 分别表示前缀最大值和后缀最大值。这个转化是在枚举每一个位置上有多高的水柱。
更新 \(a_i\) 时,单独更新每一个位置的 \(f_i,g_i\) 是比较好更新的,可是它俩的 min 值就不是很方便更新,能不能进一步转化?
我们发现,\(f_i,g_i\)本来就是左右的最大值,这俩已经包括了全局所有的数,那么对它们取个min,是不是跟全局次大值有关呢。
只要能想到这一点,不难转化成下式:\(min(f_i,g_i) = f_i+g_i-max(f_i,g_i)=f_i+g_i-max_i\),\(max_i\) 表示全局最大值。\(ans = \sum\limits_{i=1}^nf_i+g_i-max_i-a_i\)。
因为 \(a_i\) 的修改只有增加,这就方便了我们对这三个值的维护,比如 \(f_i\) 是单增的,就可以用 set 维护,相同的数值只需在 set 中记录第一个位置。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=501010;
const ll inf=0x3f3f3f3f3f3f3f3f;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
ll T;
ll n,Q;
struct E{
ll wei,zhi;
};
inline bool operator < (E A,E B) { if(A.wei!=B.wei) return A.wei < B.wei; return A.zhi > B.zhi; }
set <E> s;
ll a[N],b[N];
ll X[N],Y[N];
ll ans[N];
ll tot,ma,F,G;
void chushihua() {
tot = ma = F = G = 0;
for(ll i=1;i<=Q;i++) ans[i] = 0;
s.clear();
}
void outing() {
cout<<"G = "<<G<<endl;
for(auto v : s) {
cout<<v.wei<<","<<v.zhi<<" ";
}
cout<<endl;
}
int main() {
T = read();
while(T--) {
chushihua();
n = read();
for(ll i=1;i<=n;i++) b[i] = a[i] = read(), tot += a[i], ma = max(a[i],ma);
Q = read();
for(ll i=1;i<=Q;i++) {
X[i] = read(); Y[i] = read();
a[X[i]] += Y[i]; ma = max(ma,a[X[i]]);
tot += Y[i];
ans[i] -= tot + ma * n;
// cout<<"i = "<<i<<" tot = "<<tot<<" ma = "<<ma<<endl;
}
a[n+1] = a[0] = inf;
for(ll i=1;i<=n;i++) a[i] = b[i];
ll now = 0;
s.insert({0,0});
for(ll i=1;i<=n+1;i++) {
if(a[i]>now) {
now = a[i];
E lst = *(--s.end());
F += (i - lst.wei) * lst.zhi;
s.insert({i,a[i]});
}
}
for(ll i=1;i<=Q;i++) {
a[X[i]] += Y[i];
E wo = {X[i], a[X[i]]};
auto it = --s.lower_bound(wo);
if((*it).zhi < a[X[i]]) {
ll shang = (*it).zhi;
s.insert(wo);
it = ++s.find(wo);
F -= shang * (it->wei - wo.wei);
while(it->zhi <= wo.zhi) {
auto nxt = ++it; it--;
F -= (nxt->wei - it->wei) * it->zhi;
s.erase(it);
it = nxt;
}
F += (it->wei - wo.wei) * wo.zhi;
}
ans[i] += F;
}
s.clear();
for(ll i=1;i<=n;i++) a[i] = b[i];
now = 0;
s.insert({-n-1,0});
for(ll i=n;i>=0;i--) {
if(a[i]>now) {
now = a[i];
E lst = *(--s.end());
G += (-i - lst.wei) * lst.zhi;
s.insert({-i, a[i]});
}
}
for(ll i=1;i<=Q;i++) {
a[X[i]] += Y[i];
E wo = {-X[i], a[X[i]]};
auto it = --s.lower_bound(wo);
if((*it).zhi < a[X[i]]) {
ll shang = (*it).zhi;
s.insert(wo);
it = ++s.find(wo);
G -= shang * (it->wei - wo.wei);
while(it->zhi <= wo.zhi) {
auto nxt = ++it; it--;
G -= (nxt->wei - it->wei) * it->zhi;
s.erase(it);
it = nxt;
}
G += (it->wei - wo.wei) * wo.zhi;
}
ans[i] += G;
}
for(ll i=1;i<=Q;i++) cout<<ans[i]<<"\n";
}
return 0;
}
D:
凸优化的树上DP。
设 \(f[u][i]\) 表示 u 这棵子树所有分支都有 i 个黑点的最小修改量,\(g[u][0/1]\) 表示把 u 这个点改成红点/黑点的代价。
有如下转化:\(f[u][i] = min(g[u][1]+\sum f[v][i-1],g[u][0]+\sum f[v][i])\) 。
i 的取值是 u 子树中最短的分支,也就是说复杂度就是所有子树中最短分支的长度和,但显然当数据是链状结构时会卡死。
喜欢拿金牌的选手们会发现,这是个可以凸优化的DP,由于 DP 转移式子是将两个序列进行 (min,+) 卷积,满足闵可夫斯基和的形式,也就是两个凸包相加融合(这个在几何题中也比较常见),两个凸序列卷完还是凸序列,g 因为只有两个值所以一定是凸的,f 通过归纳自然也能证明是凸序列。
那么凸序列有什么好处呢?我们可以用 “差分序列加首项” 来维护一个凸序列,如果凸序列存在最小值,那么差分数列是单增的。回想几何中的闵可夫斯基和,我们将两个凸包上的每一条线段合在一起按斜率进行排序,序列上的 (min,+) 卷积也是如此,将首项相加,再将两个差分数列的每一个数合在一起排序,生成新的差分数列,即为卷积后 \(f[u]\) 的序列。
回到这题的做法,我们每次求 \(f[u]\) 序列,需要先将 \(f[v]\) 直接相加,再和 \(g[u]\) 做卷积,\(f[v]\) 直接相加只需要保留最短分支的长度即可(下面的代码中用 \(sho[u]\) 表示)。
这个做法最大的好处是,我们不会被链之类的数据卡掉,当我们只有一个儿子时,直接继承它的 \(f[v]\) 数列即可。
然后是和 \(g[u]\) 做卷积,分为两种:当 u 是红点时,往差分数列加入一个1;当 u 是黑点时,首项加1,再往差分数列加入一个-1。
最终答案,也就是 \(f[1]\) 的最小值,等于首项加上差分数列最小前缀(所有负数相加)。为了避免使用平衡树维护,我们观察发现加入的数字只有1和-1,我们换个简单的写法,用三个vector来记录差分数列,一个从小到大记录负数,一个记录0,一个从大到小记录正数,加入1和-1只需在vector尾部插入即可。
思路可能比较经典,但是思考和实现难度都是妥妥的金牌题。
#include <bits/stdc++.h>
#define ll long long
#define QWQ cout<<"QwQ"<<endl;
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
using namespace std;
const ll N=501010;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
struct E{
ll a0,he;
vector <ll> fu;
vector <ll> li;
vector <ll> zh;
}f[N],ling;
E *F[N]; // F[u] 指针指向儿子的f数列,便于直接继承。
ll T;
ll n;
char s[N];
ll cl[N];
vector <ll> e[N];
ll len[N],sho[N];
ll ans[N];
ll a[N];
void chushihua() {
for(ll i=1;i<=n;i++) len[i] = sho[i] = 0, e[i].clear();
for(ll i=1;i<=n;i++) f[i] = ling;
}
void DFS(ll u) {
len[u] = 1;
FOR() {
ll v = e[u][i];
DFS(v);
len[u] = max(len[u],len[v]+1);
F[u] = F[v];
if(!sho[u] || sho[v]+1 < sho[u]) sho[u] = sho[v]+1;
}
}
void TREE(ll u) {
FOR() TREE(e[u][i]);
if(e[u].size()>=2) {
for(ll i=0;i<=sho[u];i++) a[i] = 0;
for(ll v : e[u]) {
a[0] += (*F[v]).a0;
ll now = 1;
for(ll w : (*F[v]).fu) {
a[now] += w;
now++;
if(now>sho[u]) break;
}
if(now>sho[u]) continue;
for(ll w : (*F[v]).li) {
now++;
if(now>sho[u]) break;
}
if(now>sho[u]) continue;
for(ll i=(*F[v]).zh.size()-1;i>=0;i--) {
ll w = (*F[v]).zh[i];
a[now] += w;
now++;
if(now>sho[u]) break;
}
}
*F[u] = ling;
(*F[u]).a0 = a[0];
for(ll i=1;i<=sho[u];i++) {
if(a[i]<0) (*F[u]).fu.push_back(a[i]), (*F[u]).he += a[i];
else if(a[i]==0) (*F[u]).li.push_back(0);
}
for(ll i=sho[u];i>=1;i--) {
if(a[i]>0) (*F[u]).zh.push_back(a[i]);
else break;
}
}
if(cl[u]) (*F[u]).a0++, (*F[u]).fu.push_back(-1), (*F[u]).he--;
else (*F[u]).zh.push_back(1);
ans[u] = (*F[u]).a0 + (*F[u]).he;
}
int main() {
T = read();
while(T--) {
chushihua();
n = read();
scanf("%s",s+1);
for(ll i=1;i<=n;i++) cl[i] = s[i] - '0', F[i] = &f[i];
for(ll i=2;i<=n;i++) e[read()].push_back(i);
DFS(1);
TREE(1);
for(ll i=1;i<=n;i++) cout<<ans[i]<<" \n"[i==n];
}
return 0;
}