Codeforces题解集
Codeforces 2019年12月19日到 2020年2月12日 的部分比赛题
Educational Codeforces Round 82 (Rated for Div. 2)
给出m(≤1e5)个盒子,盒子的大小是2的幂次。可以选择把一个盒子分成大小相同的两部分,问最少执行几次分盒子的操作,可以装满大小为n(≤1e18)的背包。
把n转化为二进制,代表可以由若干种2的幂次的盒子各一个装满。从小往大贪心地使用已有的盒子,不足时把第一个比它大的盒子分开。
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5
6 const int maxn = 1e6 + 5;
7
8 ll t, n, m, a[maxn];
9 ll num[70];
10
11 int main() {
12 ios::sync_with_stdio(false);
13 cin.tie(nullptr);
14 cin >> t;
15 while (t--) {
16 cin >> n >> m;
17 ll tot = 0;
18 memset(num, 0, sizeof(num));
19 inc(i, 0, m - 1) {
20 cin >> a[i];
21 tot += a[i];
22 num[(int)log2((double)a[i])]++;
23 }
24 if (tot < n) {
25 cout << "-1\n";
26 continue;
27 }
28 ll ans = 0;
29 for (int i = 0; n; i++) {
30 if (n & 1) {
31 if (num[i])
32 num[i]--;
33 else
34 inc(j, i + 1, 31) if (num[j]) {
35 num[j]--;
36 num[i] += (1 << (j - i)) - 1;
37 ans += j - i;
38 break;
39 }
40 }
41 num[i + 1] += num[i] / 2;
42 n >>= 1;
43 }
44 cout << ans << "\n";
45 }
46 }
给出两个串s, t,长度≤400,问s的两个不相交的子序列能否拼接成 t.
枚举 t 的分界处,设左子串为t1,右子串为t2,遍历s,其实就是考虑是放到t1还是t2。dp[i]表示已匹配到t1[i]时,t2最远匹配到t2[dp[i]],当dp[cnt]=size(t)时表示匹配成功。时间复杂度O(n^3)
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5
6 const int maxn = 1e3 + 5;
7
8 int c;
9 char s[maxn], t[maxn];
10
11 int main() {
12 // ios::sync_with_stdio(false);
13 // cin.tie(nullptr);
14 cin >> c;
15 while (c--) {
16 scanf("%s%s", s, t + 1);
17 int l1 = strlen(s), l2 = strlen(t + 1), f = 0;
18 inc(cnt, 1, l2) {
19 vector<int> dp(cnt + 1, -1), nxt(cnt + 1);
20 dp[0] = cnt;
21 inc(i, 0, l1 - 1) {
22 inc(j, 0, cnt) nxt[j] = dp[j];
23 inc(j, 0, cnt - 1) if (s[i] == t[j + 1] && dp[j] != -1)
24 nxt[j + 1] = max(nxt[j + 1], dp[j]);
25 inc(j, 0,
26 cnt) if (dp[j] != -1 && dp[j] < l2 && s[i] == t[dp[j] + 1])
27 nxt[j] = max(nxt[j], dp[j] + 1);
28 inc(j, 0, cnt) dp[j] = nxt[j];
29 }
30 if (dp[cnt] == l2) {
31 f = 1;
32 break;
33 }
34 }
35 if (f)
36 printf("YES\n");
37 else
38 printf("NO\n");
39 }
40 }
Codeforces Round #618 (Div. 1)
定义f(x, y) = x | y - y,给出一个数列,要改变顺序顺序后,使得f(f(x1, x2), x3), …)最大
这种题目直接考虑01的四种组合结果会是什么。发现只有f(1, 0)结果会是1,所以对于某一数位,只有1 0 0 …是有意义的。找到最高的数位满足这种状况,在确定了第一个数后,后面的数无论是什么顺序都不影响了。
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5 #define dec(i, l, r) for (int i = l; i >= r; i--)
6
7 const int maxn = 1e6 + 5;
8
9 int a[maxn], n, vis[maxn];
10
11 int main() {
12 ios::sync_with_stdio(false);
13 cin.tie(nullptr);
14 cin >> n;
15 inc(i, 0, n - 1) cin >> a[i];
16 int res = -1;
17 dec(i, 30, 0) {
18 int pos = -1;
19 inc(j, 0, n - 1) if (a[j] >> i & 1) {
20 if (pos == -1)
21 pos = j;
22 else
23 pos = -2;
24 }
25 if (pos >= 0) {
26 res = pos;
27 break;
28 }
29 }
30 if (res >= 0) {
31 cout << a[res] << " ";
32 vis[res] = 1;
33 }
34 inc(i, 0, n - 1) if (!vis[i]) cout << a[i] << " ";
35 }
给出一个凸多边形P,我们可以看作一个空心的框,想像(0, 0) 处有一个钉子,框在保证钉子在里面的前提下平移,经过的区域形成一个新的多边形T。问P与T是否相似。
另一种定义,P中存在向量(x, y),则点(x, y)在T内(上)。
考虑到存在向量(x,y)就必存在(-x,-y),T必然是中心对称。那么当P是中心对称时,使P中心点在原点,可以发现若(x, y)在P上,则(2x, 2y)在T上;而对于P外的某点,考虑中心对称的且在边上的两点,构成向量(x, y),必不存在该方向上更长的向量(P是凸包)。所以P与T相似,且相似比为1:2。
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5
6 const int maxn = 1e6 + 5;
7
8 ll x[maxn], y[maxn];
9 int n;
10
11 int main() {
12 ios::sync_with_stdio(false);
13 cin.tie(nullptr);
14 cin >> n;
15 inc(i, 0, n - 1) cin >> x[i] >> y[i];
16 if (n & 1) {
17 printf("no\n");
18 exit(0);
19 }
20 ll cx = x[0] + x[n / 2], cy = y[0] + y[n / 2];
21 inc(i, 1,
22 n / 2 - 1) if (x[i] + x[i + n / 2] != cx || y[i] + y[i + n / 2] != cy) {
23 printf("no\n");
24 exit(0);
25 }
26 printf("yes\n");
27 }
给出一个数列,可以使一个连续区间的数,都变成该区间的平均数。问经过任意次操作,字典序最小的结果
考虑我们总是优先使第一个最小化,第一个最小化后,继续检查后面能否更小。维护一个递增的单调栈,出现逆序就合并。
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5 #define dec(i, l, r) for (int i = l; i >= r; i--)
6
7 const int maxn = 1e6 + 5;
8
9 ll a[maxn];
10 int n;
11
12 struct p {
13 double ave;
14 int num;
15 };
16 stack<p> s;
17 vector<double> res;
18
19 int main() {
20 ios::sync_with_stdio(false);
21 cin.tie(nullptr);
22 cin >> n;
23 inc(i, 1, n) cin >> a[i];
24 inc(i, 1, n) {
25 double sum = a[i];
26 int num = 1;
27 while (!s.empty() && sum / num < s.top().ave) {
28 sum += s.top().ave * s.top().num;
29 num += s.top().num;
30 s.pop();
31 }
32 s.push({sum / num, num});
33 }
34 while (!s.empty()) {
35 inc(i, 1, s.top().num) res.push_back(s.top().ave);
36 s.pop();
37 }
38 dec(i, n - 1, 0) printf("%.12f ", res[i]);
39 }
Educational Codeforces Round 81 (Rated for Div. 2)
给出a和m,1≤a<m≤1e10,问有多少个x,0≤x<m,gcd(a, m)==gcd(a+x, m)
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5
6 int t;
7 ll a, m;
8
9 const int maxnum = 1e5;
10 int prim[maxnum], pvis[maxnum + 5], pcnt;
11 void getprim() {
12 for (int i = 2; i <= maxnum; i++) {
13 if (!pvis[i]) prim[++pcnt] = i;
14 for (int j = 1; j <= pcnt && prim[j] * i <= maxnum; j++) {
15 pvis[prim[j] * i] = 1;
16 if (i % prim[j] == 0) break;
17 }
18 }
19 }
20
21 ll cal(ll x) {
22 ll org = x;
23 for (int i = 1; i <= pcnt && x > 1; i++) {
24 if (x % prim[i] == 0) org = org / prim[i] * (prim[i] - 1);
25 while (x % prim[i] == 0) x /= prim[i];
26 }
27 if (x > 1) org = org / x * (x - 1);
28 return org;
29 }
30
31 int main() {
32 getprim();
33 cin >> t;
34 while (t--) {
35 cin >> a >> m;
36 cout << cal(m / __gcd(a, m)) << "\n";
37 }
38 }
给出一个n的排列,2≤n≤2e5,每个数有权值ai,现要分成两个非空子串,并对应两个数字集合,通过付出每个数字的权值的代价,可以把一个数字从原来的集合移动到另一个集合里。要使左子串对应的数字集合里的所有数都小于右字串对应的集合,或使若某一集合为空集,问最小代价
易知最终左子串对应的集合必然是1,2,,,n的一个前缀,扫描一遍,维护一个“变成前缀为i的集合需要的代价”的线段树
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5
6 const int maxn = 2e5 + 5;
7
8 int n, p[maxn], a[maxn];
9 int val[maxn];
10 ll fv;
11
12 ll res, tmp;
13
14 ll w[4 * maxn], f[4 * maxn];
15
16 void build(int k, int l, int r) {
17 if (l == r) {
18 fv += val[l];
19 w[k] = fv;
20 return;
21 }
22 int m = (l + r) / 2;
23 build(2 * k, l, m);
24 build(2 * k + 1, m + 1, r);
25 w[k] = min(w[2 * k], w[2 * k + 1]);
26 }
27
28 void down(int k, int l, int r) {
29 f[2 * k] += f[k];
30 f[2 * k + 1] += f[k];
31 w[2 * k] += f[k];
32 w[2 * k + 1] += f[k];
33 f[k] = 0;
34 }
35
36 void change(int k, int l, int r, int a, int b, int val) {
37 if (a <= l && r <= b) {
38 w[k] += val;
39 f[k] += val;
40 return;
41 }
42 if (f[k]) down(k, l, r);
43 int m = (l + r) / 2;
44 if (a <= m) change(2 * k, l, m, a, b, val);
45 if (b > m) change(2 * k + 1, m + 1, r, a, b, val);
46 w[k] = min(w[2 * k], w[2 * k + 1]);
47 }
48
49 int main() {
50 cin >> n;
51 inc(i, 1, n) cin >> p[i];
52 inc(i, 1, n) {
53 cin >> a[i];
54 val[p[i]] = a[i];
55 }
56 build(1, 0, n);
57 res = a[1];
58 for (int i = 1; i < n; i++) {
59 change(1, 0, n, 0, p[i] - 1, a[i]);
60 change(1, 0, n, p[i], n, -a[i]);
61 res = min(res, w[1]);
62 }
63 cout << res;
64 }
Educational Codeforces Round 80 (Rated for Div. 2)
给出N×M的矩阵,N≤3e5,M≤8。现取出其中两行,“合并”成新的一行。所谓合并,是在相同位置的数取最大值。现在要最大化该数列的最小值,输出选取的两行行号
比较容易想到是二分答案。judge时要让每一个位置至少有一个数大于ans,可以把大于ans的数位hash成1,然后在至多2**M的hash数组里寻找是否有满足条件的两列。这里降低时间复杂度是利用了2**M<N的特点。注意二分的时候,避免没有judge为true(即ans实际为0,却不会运行到一步)的情况
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5 #define pii pair<int, int>
6 #define fi first
7 #define se second
8 #define pb push_back
9
10 const int maxn = 3e5 + 5;
11
12 int a[maxn][10], n, m, r1, r2;
13 int h[300];
14
15 inline bool judge(int ans) {
16 memset(h, 0, sizeof(h));
17 inc(i, 0, n - 1) {
18 int val = 0;
19 inc(j, 0, m - 1) {
20 if (a[i][j] >= ans) val += 1 << j;
21 }
22 h[val] = i + 1;
23 }
24 int sz = (1 << m) - 1;
25 inc(i, 0, sz) {
26 inc(j, 0, sz) if ((i | j) == sz && h[i] && h[j]) {
27 r1 = h[i], r2 = h[j];
28 return true;
29 }
30 }
31 return false;
32 }
33
34 int main() {
35 scanf("%d %d", &n, &m);
36 inc(i, 0, n - 1) inc(j, 0, m - 1) scanf("%d", &a[i][j]);
37 int l = 0, r = (int)1e9 + 1, ans = -1;
38 while (l + 1 < r) {
39 int m = (r - l) / 2 + l;
40 if (judge(m))
41 l = m, ans = m;
42 else
43 r = m;
44 }
45 if (ans == -1)
46 printf("1 1\n");
47 else
48 cout << r1 << " " << r2;
49 }
初始为1,2,,,n的一个排列,代表n位好友的消息列表。给出m条消息,其中每一条都会使对应的好友消息在列表中置顶,即排列中的该数字被提前到第一个位置。问每个人在消息列表中最靠前和最靠后的位置。
考虑到一个人的位置只有在发消息时会减少,否则只会递增。所以,只关心在发消息时以及结束时刻他前面有多少人。建立一个n+m的BIT,初始时后n个位置置为1,代表了这n个人;每次处理一位好友消息时,查询前缀和,更新最大值,把他置于所有人前一位,并删去原来所在位置.
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5 #define lowbit(x) x&(-x)
6
7 const int maxn = 6e5 + 5;
8
9 int n, m, q;
10
11 int f[maxn];
12 void add(int x, int val) {
13 for (; x <= n + m; x += lowbit(x)) f[x] += val;
14 }
15 int get(int x) {
16 int res = 0;
17 for (; x; x -= lowbit(x)) res += f[x];
18 return res;
19 }
20
21 int a[maxn], b[maxn], pos[maxn];
22
23 int main() {
24 cin >> n >> m;
25 inc(i, 1, n) {
26 a[i] = b[i] = i;
27 pos[i] = m + i;
28 add(i + m, 1);
29 }
30 inc(i, 1, m) {
31 cin >> q;
32 a[q] = 1;
33 b[q] = max(b[q], get(pos[q] - 1) + 1);
34 add(pos[q], -1);
35 pos[q] = m - i + 1;
36 add(m - i + 1, 1);
37 }
38 inc(i, 1, n) b[i] = max(b[i], get(pos[i] - 1) + 1);
39 inc(i, 1, n) printf("%d %d\n", a[i], b[i]);
40 }
Codeforces Round #610 (Div. 2)
有一次测验,有N道题,考试时间T,已知每一题是容易题还是困难题,解决前者需要时间a,后者需要b,每道题有个ti,考生在离开考场(可以选择提前离场)时若不小于该时间而未解决该题,他总分就是0分。问考生最多能获得多少分(解决一题得一分)。
思维点在于考虑到每个ti的前一秒是离场的最佳时间,检查能否把必须做的题了,外加做额外的题,更新答案。另外考试结束时也要检查是否可以更新答案。
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5
6 const int maxn = 2e5 + 5;
7
8 int m, n, t, a, b;
9
10 struct problem {
11 int hard, gg;
12 bool operator<(const problem& o) const { return gg < o.gg; }
13 } p[maxn];
14
15 int main() {
16 ios::sync_with_stdio(false);
17 cin.tie(nullptr);
18 cin >> m;
19 while (m--) {
20 cin >> n >> t >> a >> b;
21 ll ea = 0, eb = 0;
22 inc(i, 0, n - 1) {
23 cin >> p[i].hard;
24 ea += p[i].hard == 0;
25 eb += p[i].hard == 1;
26 }
27 inc(i, 0, n - 1) cin >> p[i].gg;
28 sort(p, p + n);
29 p[n] = {-1, t + 1};
30 ll task = 0;
31 ll na = 0, nb = 0, res = 0;
32 for (int i = 0, j; i <= n; i = j) {
33 if (p[i].gg - 1 >= task) {
34 ll tot = na + nb, tmp = p[i].gg - 1 - task;
35 if (tmp <= (ea - na) * a)
36 tot += tmp / a;
37 else {
38 tot += ea - na;
39 tmp -= (ea - na) * a;
40 if (tmp <= (eb - nb) * b)
41 tot += tmp / b;
42 else
43 tot += eb - nb;
44 }
45 res = max(res, tot);
46 }
47 j = i;
48 while (j <= n && p[j].gg == p[i].gg) {
49 if (p[j].hard == 0)
50 task += a, na++;
51 else
52 task += b, nb++;
53 j++;
54 }
55 }
56 cout << res << "\n";
57 }
58 }
交互题。定义Edit distance是串s经过增,删,改(每次一个字符)变成串t的次数。现有一个串s(size≤300),用户询问串t(size≤300),返回Edit distance,最多询问s.size+2次(包括最终返回0的“询问”)
先询问300个a的串和300个b的串得到s有几个a和几个b,依据:由s变成全a只能把b改成a以及添加a。此时,我们也知道了如果询问一个与s长度相同全为b的串(记作tt)会得到的答案。然后询问这样的串t,长度与s相同,除了某一位置为a,其他都是b,可以推理出,a的位置与s匹配与 询问tt的返回值-1 是等价的。又注意到最后一个位置可以通过计算a的个数得出,所以无须询问,总询问次数刚好size+2次。
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define inc(i, l, r) for (int i = l; i <= r; i++)
4
5 int a, b, x, n;
6
7 void read() {
8 cout << '\n';
9 fflush(stdout);
10 cin >> x;
11 if (x == 0) exit(0);
12 }
13
14 int main() {
15 inc(i, 1, 300) cout << 'a';
16 read();
17 a = 300 - x;
18
19 inc(i, 1, 300) cout << 'b';
20 read();
21 b = 300 - x;
22
23 n = a + b;
24 string res(n, 'b');
25
26 int ta = 0;
27 inc(i, 0, n - 2) {
28 inc(j, 0, n - 1) if (i == j) cout << "a";
29 else cout << "b";
30 read();
31 if (x == a - 1) {
32 res[i] = 'a';
33 ta++;
34 }
35 }
36 if (ta != a) res[n - 1] = 'a';
37 cout << res;
38 read();
39 }
Codeforces Round #607 (Div. 1)
给出r×c的格子,上面有字符A和P。选择一个1×X的区域,指定某一方向与步长,该方向需与指定区域垂直;该方向的格子会被与起始指定区域同一行(列)的字符同化。问最少经过几次操作全部变成A
模拟题。最多4步(或者不能),可以先判断能否3步的,其次2步,最后1步,节省代码量。
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5
6 char a[65][65];
7 int t, w, h;
8
9 bool judge(int x, int d) {
10 if (d == 0) {
11 inc(i, 0, w - 1) if (a[x][i] != 'A') return 0;
12 } else {
13 inc(i, 0, h - 1) if (a[i][x] != 'A') return 0;
14 }
15 return 1;
16 }
17
18 int main() {
19 ios::sync_with_stdio(false);
20 cin.tie(nullptr);
21 cin >> t;
22 while (t--) {
23 cin >> h >> w;
24 int all = 0;
25 inc(i, 0, h - 1) {
26 cin >> a[i];
27 inc(j, 0, w - 1) all += a[i][j] == 'A';
28 }
29 if (all == 0)
30 printf("MORTAL\n");
31 else if (all == h * w)
32 printf("0\n");
33 else {
34 int res = 4;
35 inc(i, 1, h - 2) if (a[i][0] == 'A' || a[i][w - 1] == 'A') res = 3;
36 inc(i, 1, w - 2) if (a[0][i] == 'A' || a[h - 1][i] == 'A') res = 3;
37 inc(i, 1, h - 2) if (judge(i, 0)) res = 2;
38 inc(i, 1, w - 2) if (judge(i, 1)) res = 2;
39 if (a[0][0] == 'A' || a[0][w - 1] == 'A' || a[h - 1][0] == 'A' ||
40 a[h - 1][w - 1] == 'A')
41 res = 2;
42 if (judge(0, 0) || judge(h - 1, 0) || judge(0, 1) ||
43 judge(w - 1, 1))
44 res = 1;
45 printf("%d\n", res);
46 }
47 }
48 }
给出一个2n个点的树,两两匹配树上的所有节点,匹配后这些节点对的距离总和,问该距离总和的最小值与最大值。
考虑以某一条边为界,把树分为两部分。要最大化距离总和时,要尽可能匹配不同部分的点,那么这条边就被计算min(sz, 2n-sz)次;,sz为一部分的节点数;要最小化距离总和时,要尽可能匹配同一部分的点,那么这条边就被计算 sz & 1 次。
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5 #define pii pair<int, int>
6 #define fi first
7 #define se second
8 #define pb push_back
9
10 const int maxn = 2e5 + 5;
11
12 vector<pii> g[maxn];
13
14 int t, n, u, v, dis;
15 ll res1, res2;
16
17 int dfs(int x, int par) {
18 int sz = 1;
19 for (int i = 0; i < g[x].size(); i++) {
20 if (g[x][i].fi != par) {
21 int t = dfs(g[x][i].fi, x);
22 res1 += t % 2 * g[x][i].se;
23 res2 += (ll)min(t, 2 * n - t) * g[x][i].se;
24 sz += t;
25 }
26 }
27 return sz;
28 }
29
30 int main() {
31 ios::sync_with_stdio(false);
32 cin.tie(nullptr);
33 cin >> t;
34 while (t--) {
35 cin >> n;
36 inc(i, 1, 2 * n) g[i].clear();
37 inc(i, 1, 2 * n - 1) {
38 cin >> u >> v >> dis;
39 g[u].pb(pii(v, dis));
40 g[v].pb(pii(u, dis));
41 }
42 res1 = res2 = 0;
43 dfs(1, -1);
44 printf("%lld %lld\n", res1, res2);
45 }
46 }
Educational Codeforces Round 78 (Rated for Div. 2)
给出n个区间,这些区间的端点取遍1-2n。当两个区间相交时(不能包含),它们代表的节点之间就有一条边。判断依此生成的图是否是树。
对所有节点排序,遇到左端点加入set,遇到右端点时统计与其左端点间有多少个点,并删除左端点。用并查集维护集合关系。最后根据边数和连通性判断是否是树。因为每次都是遍历寻找左右端点间的点,此时算法的复杂度是O(n^2)。当统计点的个数大于n-1时,一定不是树,就可以break出来,此时是O(nlogn)。
给出n个节点的树,要为每一个节点构造一个区间,使得区间的左右端点取遍1-2n,两个节点之间是否有边等价于它们的区间是否相交(不能包含)。
根的左端点为1,先序遍历,维护当前已使用的数字,当前区间的右端点贪心地取最小值——即右端点与已使用的数字之间的空余刚好能容下它的儿子的左端点(它的儿子的左端点就其次取这些空余的数字)。并且儿子的左端点越大越先遍历,这样可以保证儿子之间不会相交。
1 #include <bits/stdc++.h>
2 using namespace std;
3 #define ll long long
4 #define inc(i, l, r) for (int i = l; i <= r; i++)
5 #define pii pair<int, int>
6 #define fi first
7 #define se second
8 #define pb push_back
9
10 const int maxn = 1e6 + 5;
11
12 vector<int> g[maxn];
13 int n, u, v;
14 pii res[maxn];
15
16 int top = 1, root = 1;
17 void dfs(int x, int par) {
18 top += g[x].size();
19 if (x == root) top++;
20 res[x].se = top;
21 for (int i = 0, j = 0; i < g[x].size(); i++) {
22 if (g[x][i] != par) {
23 res[g[x][i]].fi = top - ++j;
24 }
25 }
26 for (int i = 0; i < g[x].size(); i++) {
27 if (g[x][i] != par) {
28 dfs(g[x][i], x);
29 }
30 }
31 }
32
33 int main() {
34 ios::sync_with_stdio(false);
35 cin.tie(nullptr);
36 cin >> n;
37 inc(i, 1, n - 1) {
38 cin >> u >> v;
39 g[u].pb(v);
40 g[v].pb(u);
41 }
42 dfs(1, -1);
43 res[1].fi = 1;
44 inc(i, 1, n) cout << res[i].fi << " " << res[i].se << "\n";
45 }