A - To The Max,hdu1081
求最大子矩阵和,把二维转换到一维,把每一行的某些列的和看作一个元素,这样就成了一维下的最大子段和,预处理每行的前缀和,暴力枚举每行的的列数情况即可

#include <iostream> #include <algorithm> #include <cstring> using namespace std; const int N = 110; int c[N][N], dp[N][N], n, res; int main() { while (cin >> n) { res = 0, memset(dp, 0, sizeof(dp)); for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) cin >> c[i][j]; } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) dp[i][j] = dp[i][j - 1] + c[i][j]; } for (int i = 1; i <= n; i++) { for (int j = i; j <= n; j++) { int sum = 0; for (int k = 1; k <= n; k++) { sum += dp[k][j] - dp[k][i - 1]; if (sum < 0) sum = 0; else res = max(res, sum); } } } cout << res << endl; } return 0; }
B - BUY LOW, BUY LOWER,poj1952
最长下降子序列很简单就能求出,但是还要额外求一个方案数
设$dp[i]$为以$i$结尾的最长下降子序列的长度,$num[i]$为以$i$结尾的最长下降子序列的个数
所以状态转移方程为$dp[i] = max(dp[i], dp[j] + 1)(c[j]>c[i])$,$num[i] += num[j](c[j]>c[i] && dp[j]+1==dp[i])$
但这样求出来的$num$是没有去重的,考虑到$c[j]==c[i] && dp[j]==dp[i]$时,$c[i]$可以完全被$c[j]$替代,此时$num[i]$应该等于$0$

#include <iostream> #include <algorithm> using namespace std; const int N = 5010; int n, c[N], dp[N], num[N], imax, ans; int main() { cin >> n; for (int i = 1; i <= n; i++) cin >> c[i]; for (int i = 1; i <= n; i++) { dp[i] = 1; for (int j = 1; j < i; j++) { if (c[j] > c[i]) dp[i] = max(dp[i], dp[j] + 1); } } for (int i = 1; i <= n; i++) { if (1 == dp[i]) num[i] = 1; for (int j = 1; j < i; j++) { if (c[j] > c[i] && dp[j] + 1 == dp[i]) num[i] += num[j]; else if (c[j] == c[i] && dp[j] == dp[i]) num[i] = 0; // 能够被c[j]代替,所以num[i]=0 } } for (int i = 1; i <= n; i++) imax = max(imax, dp[i]); for (int i = 1; i <= n; i++) if (imax == dp[i]) ans += num[i]; cout << imax << " " << ans << endl; return 0; }
C - Monkey and Banana,hdu1069
矩阵嵌套的变形,每个立方体有六个面,每个面都能作为底面,预处理一下,就跟矩阵嵌套一样了,排个序后动态规划

#include <iostream> #include <algorithm> using namespace std; const int N = 200; struct node { int x, y, z; }; node c[N]; int dp[N], n, cnt, imax, icas; bool cmp(const node one, const node two) { if (one.x != two.x) return one.x < two.x; else return one.y < two.y; } int main() { while (cin >> n && 0 != n) { imax = cnt = 0; for (int i = 1; i <= n; i++) { int tp_x, tp_y, tp_z; cin >> tp_x >> tp_y >> tp_z; c[++cnt].x = tp_x, c[cnt].y = tp_y, c[cnt].z = tp_z; c[++cnt].x = tp_y, c[cnt].y = tp_x, c[cnt].z = tp_z; c[++cnt].x = tp_y, c[cnt].y = tp_z, c[cnt].z = tp_x; c[++cnt].x = tp_z, c[cnt].y = tp_y, c[cnt].z = tp_x; c[++cnt].x = tp_x, c[cnt].y = tp_z, c[cnt].z = tp_y; c[++cnt].x = tp_z, c[cnt].y = tp_x, c[cnt].z = tp_y; } sort(c + 1, c + cnt + 1, cmp); for (int i = 1; i <= cnt; i++) dp[i] = c[i].z; for (int i = 1; i <= cnt; i++) { for (int j = 1; j < i; j++) { if (c[i].x > c[j].x && c[i].y > c[j].y) { dp[i] = max(dp[i], dp[j] + c[i].z); } } } for (int i = 1; i <= cnt; i++) imax = max(imax, dp[i]); cout << "Case " << ++icas << ": maximum height = " << imax << endl; } return 0; }
D - Trip,poj1934
最长公共子序列问题(LCS),当$a[i]==b[j]$时,$dp[i][j]=d[i-1][j-1]+1$,当$a[i]!=b[j]$时,$dp[i][j]=max(dp[i-1][j],dp[i][j-1])$
但是最坑的是需要输出方案数,一开始想的是暴搜,但肯定会超时
但仔细想一想,其实有很多的搜索是不必要的,比如两个串的某两个位置的字符不相等,这样就没必要继续向前一位搜索了,我们就可以直接跳到当前位置前有公共字符的位置继续搜索
所以预处理$l\_a[N][26]$和$l\_b[N][26]$数组,分别表示$a$字符串和$b$字符串在第$i$位前面第$j$个字母第一次出现的位置
如果$a$字符串和$b$字符串当前位置的字符相等,则可以向前递归,即跳到$pos\_a-1$和$pos\_b-1$进行递归
如果$a$字符串和$b$字符串当前位置的字符不相等,则没有必要向前递归,直接跳到当前位置前面第$i$个字符相等的位置,即跳到$l\_a[pos\_a][i]-1$和$l\_b[pos\_b][i]-1$进行递归

#include <iostream> #include <string> #include <algorithm> #include <cstdio> #include <vector> #include <cstring> using namespace std; const int N = 100; const int M = 26; char a[N], b[N]; int dp[N][N], l_a[N][M], l_b[N][M]; int len_a, len_b; vector<string> res; void dfs(int pos_a, int pos_b, int len, string s) { if (0 == len) { reverse(s.begin(), s.end()); res.push_back(s); return; } else if (0 == pos_a || 0 == pos_b) return; else { if (a[pos_a] == b[pos_b]) dfs(pos_a - 1, pos_b - 1, len - 1, s + a[pos_a]); else { for (int i = 0; i < M; i++) { int tp_one = l_a[pos_a][i], tp_two = l_b[pos_b][i]; if (len == dp[tp_one][tp_two]) { dfs(tp_one - 1, tp_two - 1, len - 1, s + char('a' + i)); } } } } return; } int main() { scanf("%s%s", a + 1, b + 1); len_a = strlen(a + 1), len_b = strlen(b + 1); for (int i = 1; i <= len_a; i++) { for (int j = 0; j < M; j++) { if (a[i] == char('a' + j)) l_a[i][j] = i; else l_a[i][j] = l_a[i - 1][j]; } } for (int i = 1; i <= len_b; i++) { for (int j = 0; j < M; j++) { if (b[i] == char('a' + j)) l_b[i][j] = i; else l_b[i][j] = l_b[i - 1][j]; } } for (int i = 1; i <= len_a; i++) { for (int j = 1; j <= len_b; j++) { if (a[i] == b[j]) dp[i][j] = dp[i - 1][j - 1] + 1; else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); } } dfs(len_a, len_b, dp[len_a][len_b], ""); sort(res.begin(), res.end()); for (int i = 0; i < (int)res.size(); i++) cout << res[i] << endl; return 0; }
E - Polygon,poj1179
感觉像一个加强版的成环石子合并,先来回顾一下成环的石子合并
将原序列复制一份放在后面,枚举起点$i$,用区间合并去计算区间$[i,i+n]$这个区间的最大值
这题就是把区间合并中对两个区间的操作换了下,石子合并是两个区间值相加,这题两个区间值能够相加,相乘,而且区间值可能为负,这就导致我们要记录一个最大值一个最小值
当两个区间值相乘时:
- 两个大的正区间值相乘得到一个更大的正区间值,即$imax[l][r] = max(imax[l][r], imax[l][k - 1] * imax[k][r])$
- 两个小的负区间值相乘得到一个大的正区间值,即$imax[l][r] = max(imax[l][r], imin[l][k - 1] * imin[k][r])$
- 两个小的正区间值相乘得到一个小的正区间值,即$imin[l][r] = min(imin[l][r], imin[l][k - 1] * imax[k][r])$
- 一个正区间值和一个负区间值相乘得到一个负区间值,即$imin[l][r] = min(imin[l][r], imin[l][k - 1] * imin[k][r])$
- 一个负区间值和一个正区间值相乘得到一个负区间值,即$imin[l][r] = min(imin[l][r], imax[l][k - 1] * imin[k][r])$
当两个区间相加时:
- 两个大的正区间值相加得到一个更大的正区间值,即$imax[l][r] = max(imax[l][r], imax[l][k - 1] + imax[k][r])$
- 两个小的负区间值相加得到一个更小的负区间值,即$imin[l][r] = min(imin[l][r], imin[l][k - 1] + imin[k][r])$
所以只需要把对两个区间的操作换成以上七种就跟石子合并是一样的了

#include <iostream> #include <algorithm> #include <cstring> using namespace std; const int N = 110; const int INF = 0x3f3f3f3f; int imax[N][N], imin[N][N]; int a[N], op[N], pos[N]; int n, tot, ans = -INF; char ch; int cal(int x) { memset(imax, -INF, sizeof(imax)); memset(imin, INF, sizeof(imin)); for (int i = x; i <= x + n - 1; i++) imin[i][i] = imax[i][i] = a[i]; for (int len = 1; len <= n; len++) { for (int l = x; l <= x + n - len; l++) { int r = l + len - 1; for (int k = l + 1; k <= r; k++) { if (1 == op[k]) { imax[l][r] = max(imax[l][r], imax[l][k - 1] * imax[k][r]); imax[l][r] = max(imax[l][r], imin[l][k - 1] * imin[k][r]); imin[l][r] = min(imin[l][r], imin[l][k - 1] * imax[k][r]); imin[l][r] = min(imin[l][r], imin[l][k - 1] * imin[k][r]); imin[l][r] = min(imin[l][r], imax[l][k - 1] * imin[k][r]); } else { imax[l][r] = max(imax[l][r], imax[l][k - 1] + imax[k][r]); imin[l][r] = min(imin[l][r], imin[l][k - 1] + imin[k][r]); } } } } return imax[x][x + n - 1]; } int main() { cin >> n; for (int i = 1; i <= n; i++) { cin >> ch >> a[i]; a[i + n] = a[i]; if ('x' == ch) op[i] = op[i + n] = 1; else op[i] = op[i + n] = 0; } for (int i = 1; i <= n; i++) { int tp = cal(i); if (tp == ans) pos[++tot] = i; else if (ans < tp) ans = tp, pos[tot = 1] = i; } cout << ans << endl; for (int i = 1; i <= tot; i++) { if (i == tot) cout << pos[i] << endl; else cout << pos[i] << " "; } //system("pause"); return 0; }
F - Palindrome subsequence,hdu4632
求一个字符串的回文子序列的数目
设$dp[i][j]$为字符串$s$中从$i$到$j$的回文子序列的数目
$dp[i][j]$有$dp[i+1][j]$和$dp[i][j-1]$转移过来,但考虑到有重叠部分$i+1$到$j-1$,所以$dp[i][j] = (dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] + MOD) \% MOD$
当$s[i]==s[j]$时,$s[i]$和$s[j]$与$s[i+1]$到$s[j-1]$内的回文子序列都能构成一个新的回文序列,两者本身也构成回文子序列,即$dp[i][j] = (dp[i][j] + dp[i + 1][j - 1] + 1 + MOD) \% MOD$

#include <iostream> #include <algorithm> #include <cstring> using namespace std; const int N = 1010; const int MOD = 10007; char s[N]; int dp[N][N], t, len, icas; int main() { scanf("%d", &t); while (t--) { scanf("%s", s + 1); len = strlen(s + 1); memset(dp, 0, sizeof(dp)); for (int i = 1; i <= len; i++) dp[i][i] = 1; for (int d = 1; d <= len; d++) { for (int i = 1; i + d <= len; i++) { int j = i + d; dp[i][j] = (dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] + MOD) % MOD; if (s[i] == s[j]) dp[i][j] = (dp[i][j] + dp[i + 1][j - 1] + 1 + MOD) % MOD; } } printf("Case %d: %d ", ++icas, dp[1][len]); } return 0; }
G - You Are the One,hdu4283
设$dp[l][r]$表示在不考虑其他人的情况下第$l$个人到第$r$个人产生的不开心值总和,预处理一个数组$sum[i]$表示前$i$个人的屌丝值总和
枚举$l$到$r$的分割点$k$,两种情况
- 第$l$到第$k-1$个人上台后第$k$到第$r$个人才上台,这样第$k$到第$r$个人会不但会产生在不考虑其他人的情况下$dp[k][r]$的不开心值,还会产生在等待第$l$到第$k-1$个人上台表演过程中产生的不开心值$(sum[r] - sum[k - 1]) * (k - l)$,而第$l$到第$k-1$个人只会产生不考虑其他人的情况下产生的不开心值$dp[l][k-1]$,最后有$dp[l][r]=dp[l][k - 1] + dp[k][r] + (sum[r] - sum[k - 1]) * (k - l)$
- 第$k$到第$r$个人上台后第$l$到第$k-1$的个人才上台,这样第$k$到第$r$个人只会产生不考虑其他人的情况下产生的不开心值$dp[k][r]$,而第$l$到第$k-1$个人会不但会产生在不考虑其他人的情况下$dp[l][k-1]$的不开心值,还会产生在等待第$k$到第$r$个人上台表演过程中产生的不开心值$(sum[k-1] - sum[l-1]) * (r-k+1)$,又因为栈是先进后出的,所以这种情况下第$l$个人第一个进栈,就会最后一个出栈,第$l$到第$k-1$在等待的过程中又会额外增加不高兴值,预处理一个数组$buf[i][j]$表示这种情况下第$i$到第$j$个人额外增加的不开心值,最后有$dp[l][r]=dp[l][k - 1] + dp[k][r] + (sum[k - 1] - sum[l - 1]) * (r - k + 1) + buf[l][k - 1]$
综上,$dp[l][r]=min(dp[l][r],min(dp[l][k - 1] + dp[k][r] + (sum[k - 1] - sum[l - 1]) * (r - k + 1) + buf[l][k - 1],dp[l][k - 1] + dp[k][r] + (sum[r] - sum[k - 1]) * (k - l)))$

#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 110; const int INF = 0x3f3f3f3f; int t, n, icas; int dp[N][N], d[N], sum[N], buf[N][N]; void get_buf() { for (int len = 2; len <= n; len++) { for (int i = 1; i + len - 1 <= n; i++) { int j = i + len - 1; buf[i][j] = buf[i + 1][j] + (j - i) * d[i]; } } } int main() { cin >> t; while (t--) { cin >> n; memset(dp, 0, sizeof(dp)); memset(sum, 0, sizeof(sum)); memset(buf, 0, sizeof(buf)); for (int i = 1; i <= n; i++) cin >> d[i]; for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + d[i]; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i != j) dp[i][j] = INF; } } get_buf(); for (int len = 2; len <= n; len++) { for (int l = 1; l + len - 1 <= n; l++) { int r = l + len - 1; for (int k = l + 1; k <= r; k++) { int tp_one = dp[l][k - 1] + dp[k][r] + (sum[r] - sum[k - 1]) * (k - l); // 前面的人先进 int tp_two = dp[l][k - 1] + dp[k][r] + (sum[k - 1] - sum[l - 1]) * (r - k + 1) + buf[l][k - 1]; dp[l][r] = min(dp[l][r], min(tp_one, tp_two)); } } } cout << "Case #" << ++icas << ": " << dp[1][n] << endl; } return 0; }
H - Anniversary party,poj2342
设$dp[u][0]$表示选择$u$节点时子树欢乐值的最大值,$dp[u][1]$表示不选择$u$节点时子树快乐值的最大值
设$v$是$u$的子节点,两种情况
- 不选择父节点$u$时,其所有的子节点$v$可以选也可以不选,即$dp[u][0] += max(dp[v][0], dp[v][1])$
- 选择父节点$u$时,其所有的子节点都不能选,即$dp[u][1] += dp[v][0]$

#include <iostream> #include <algorithm> #include <cstring> #include <vector> using namespace std; const int N = 6010; const int M = 2; vector<int> nex[N]; int dp[N][M], in[N]; int val[N], vis[N]; int n, a, b, res; void dfs(int u) { vis[u] = 1, dp[u][0] = 0, dp[u][1] = val[u]; for (int i = 0; i < (int)nex[u].size(); i++) { int v = nex[u][i]; if (vis[v]) continue; dfs(v); dp[u][0] += max(dp[v][0], dp[v][1]); dp[u][1] += dp[v][0]; } return; } int main() { while (cin >> n && 0 != n) { for (int i = 1; i <= n; i++) nex[i].clear(); memset(in, 0, sizeof(in)), memset(vis, 0, sizeof(vis)); for (int i = 1; i <= n; i++) cin >> val[i]; while (cin >> a >> b && 0 != (a + b)) nex[b].push_back(a), in[a]++; for (int i = 1; i <= n; i++) { if (0 == in[i]) { dfs(i); res = max(dp[i][0], dp[i][1]); break; } } cout << res << endl; } return 0; }
I - Accumulation Degree,poj3585
换根的板子都差不多,用链式前向星存图,关键是公式如何推导
设$dp[u]$表示第一次深搜得出每个节点最大的流量,$res[u]$表示以$u$为根节点时该结点最大的流量
第二次深搜,对于每一个子节点$v$有两种情况
- 当这个节点的父节点$u$的度为$1$时,节点$u$只有一个子节点$v$,此时状态转移方程$res[v] = dp[v] + edge[i].val$
- 当这个节点的父节点$u$的度不为$1$时,节点$u$不止一个子节点$v$,所以先要求出除$v$节点以外所以$u$节点的子节点的流量和$res[u] - min(dp[v], edge[i].val)$,画图后,得出状态转移方程$res[v] = dp[v] + min(edge[i].val, res[u] - min(dp[v], edge[i].val))$

#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> using namespace std; const int N = 200010; struct node { int to, nex, val; }; node edge[2 * N]; int dp[N], res[N], vis[N], head[N], deg[N]; int t, n, a, b, c, cnt, ans; void init() { cnt = ans = 0; memset(dp, 0, sizeof(dp)); memset(res, 0, sizeof(res)); memset(head, 0, sizeof(head)); memset(deg, 0, sizeof(deg)); } void add_edge(int u, int v, int w) { edge[++cnt].to = v; edge[cnt].val = w; edge[cnt].nex = head[u]; head[u] = cnt; } void dp_max(int u) { vis[u] = 1; for (int i = head[u]; 0 != i; i = edge[i].nex) { int v = edge[i].to; if (vis[v]) continue; dp_max(v); if (1 == deg[v]) dp[u] += edge[i].val; else dp[u] += min(dp[v], edge[i].val); } return; } void dfs(int u) { vis[u] = 1; for (int i = head[u]; 0 != i; i = edge[i].nex) { int v = edge[i].to; if (vis[v]) continue; if (1 == deg[u]) res[v] = dp[v] + edge[i].val; else res[v] = dp[v] + min(edge[i].val, res[u] - min(dp[v], edge[i].val)); dfs(v); } return; } int main() { scanf("%d", &t); while (t--) { init(); scanf("%d", &n); for (int i = 1; i < n; i++) { scanf("%d%d%d", &a, &b, &c); add_edge(a, b, c), add_edge(b, a, c); deg[a] += 1, deg[b] += 1; } memset(vis, 0, sizeof(vis)); dp_max(1); res[1] = dp[1]; memset(vis, 0, sizeof(vis)); dfs(1); for (int i = 1; i <= n; i++) ans = max(ans, res[i]); printf("%d ", ans); } return 0; }
J - Computer,hdu2196
换根的题,不过不能只记录一个根节点到叶子节点的最大值,还要记录一个根节点到到叶子节点的第二大的值,顺便记录一下每个节点的最大值是从哪个子节点转移过来的
设$maxn[i]$和$smaxn[i]$分别表示$i$节点到叶子节点的最大值和第二大的值,$m\_id[i]$和$sm\_id[i]$分别表示$i$节点的最大值和第二大的值从哪个节点转移过来
当从父节点$u$向子节点$v$转移的时候,有两种情况
- 如果父节点$u$的最大值是从子节点$v$转移过来的,那么就需要判断$smaxn[u] + edge[i].val$和$smaxn[v]$的大小关系,如果前者比后者大,则更新$smaxn[v]$和$sm\_id[v]$后再判断$smaxn[v]$和$maxn[v]$的大小关系
- 如果父节点$u$的最大值不是从子节点$v$转移过来的,那么就需要判断$maxn[u] + edge[i].val$和$smaxn[v]$的大小关系,如果前者比后者大,则更新$smaxn[v]$和$sm\_id[v]$后再判断$smaxn[v]$和$maxn[v]$的大小关系

#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int N = 100010; struct node { int to, nex, val; }; int n, a, w; node edge[N]; int head[N], cnt; int smaxn[N], maxn[N]; int sm_id[N], m_id[N]; //sm_id[i]=j表示i节点从j节点转移过来 void add_edge(int u, int v, int w) { edge[++cnt].to = v; edge[cnt].val = w; edge[cnt].nex = head[u]; head[u] = cnt; } void dp(int u, int p) { smaxn[u] = maxn[u] = 0; for (int i = head[u]; 0 != i; i = edge[i].nex) { int v = edge[i].to; if (v == p) continue; dp(v, u); if (maxn[v] + edge[i].val > smaxn[u]) { smaxn[u] = maxn[v] + edge[i].val; sm_id[u] = v; if (smaxn[u] > maxn[u]) { swap(smaxn[u], maxn[u]); swap(sm_id[u], m_id[u]); } } } return; } void dfs(int u, int p) { for (int i = head[u]; 0 != i; i = edge[i].nex) { int v = edge[i].to; if (v == p) continue; if (v == m_id[u]) { if (smaxn[u] + edge[i].val > smaxn[v]) { smaxn[v] = smaxn[u] + edge[i].val; sm_id[v] = u; if (smaxn[v] > maxn[v]) { swap(smaxn[v], maxn[v]); swap(sm_id[v], m_id[v]); } } } else { if (maxn[u] + edge[i].val > smaxn[v]) { smaxn[v] = maxn[u] + edge[i].val; sm_id[v] = u; if (smaxn[v] > maxn[v]) { swap(smaxn[v], maxn[v]); swap(sm_id[v], m_id[v]); } } } dfs(v, u); } return; } int main() { while (scanf("%d", &n) != EOF) { cnt = 0; memset(head, 0, sizeof(head)); for (int i = 2; i <= n; i++) { scanf("%d%d", &a, &w); add_edge(i, a, w), add_edge(a, i, w); } dp(1, -1); dfs(1, -1); for (int i = 1; i <= n; i++) printf("%d ", maxn[i]); } return 0; }
K - Tree Painting,CodeForces1187E
设$sum[i]$表示第一次深搜后第$i$个节点子树的节点数量,设$res[i]$表示以$i$为根节点时答案
假设第一次从节点$1$开始深搜,则有$res[1]=n+dp[2]+dp[3]+...+dp[n]$
当从$1$转移到它的子节点$2$时有$res[2]=n+dp[3]+...+dp[n]+dp[1]-dp[2]$
则有$res[1]=res[2]+dp[1]-2*dp[2]$,推广得$res[v]=res[u]+dp[1]-2*dp[v]$

#include <iostream> #include <algorithm> #include <cstring> using namespace std; const int N = 200010; struct node { int to, nex; }; node edge[2 * N]; long long dp[N], res[N], ans; int vis[N], head[N]; int n, a, b, cnt; void add_edge(int u, int v) { edge[++cnt].to = v; edge[cnt].nex = head[u]; head[u] = cnt; } void dp_max(int u) { dp[u] = 1; vis[u] = 1; for (int i = head[u]; 0 != i; i = edge[i].nex) { int v = edge[i].to; if (vis[v]) continue; dp_max(v); dp[u] += dp[v]; } return; } void dfs(int u) { vis[u] = 1; for (int i = head[u]; 0 != i; i = edge[i].nex) { int v = edge[i].to; if (vis[v]) continue; res[v] = res[u] + dp[1] - 2 * dp[v]; dfs(v); } return; } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { scanf("%d%d", &a, &b); add_edge(a, b), add_edge(b, a); } dp_max(1); memset(vis, 0, sizeof(vis)); for (int i = 1; i <= n; i++) res[1] += dp[i]; dfs(1); for (int i = 1; i <= n; i++) ans = max(ans, res[i]); printf("%lld ", ans); return 0; }