问题 B: Problem 2
时间限制: 2 Sec 内存限制: 256 MB
提交: 179 解决: 13
[提交][状态]
题目描述
你在一棵由n个点组成的树(一个无向连通的无环图)上玩游戏。
起初,所有点都是白色的。游戏的第一步,你选择一个点并把它染成黑色。然后,接下来的每一步中,选择一个与某个黑色点相邻的白色点,并将其染为黑色。
每次选择点时,都会获得与 所选点联通的白色点的数目(包括所选点) 相等的分数。游戏结束时,所有的点都被染成黑色。
下面是一个示例:
点1和4已经被染黑。如果你选择点2,你会获得4分(联通的白色点有2,3,5,6).如果你选择点9,你会获得3分(联通的白色点有7,8,9)。
你的任务是使得分最大。
Input
第一行包含一个整数n(2≤n≤2×105)代表树包含的点数。
接下来n-1行每行描述了树的一条边。第i行两个整数ui和vi代表ui和vi之间有边相连
(1≤ui,vi≤n,ui≠vi)。
Output
输出一行一个整数,你能得到的最大得分。
Example
input
9
1 2
2 3
2 5
2 6
1 4
4 9
9 7
9 8
output
36
input
5
1 2
1 3
2 4
2 5
output
14
Note
第一个例子中,树的形态如题目描述所示。
Hint
对于30%的数据,n≤2,000
对于另外20%的数据,保证原图是一条链
林肯是大头:http://www.accoders.com/problem.php?cid=1985&pid=1
思路:
1.一道树上换根问题
首先发现答案就是以一个点为根的其所有子树的根的儿子个数之和
进一步考虑,需要枚举每一个点进行比较,时间复杂度为O(N*(N+M))
所以考虑进行换根
一边深搜后得到以1为根的答案,存到一个数组ans中
接着进行下一次深搜,每一个非原根的点的答案由其父亲转移而来
ans[x]=ans[fa]+n-2*siz[x];
-siz[x]:
实际上就是去掉以其父亲为根时以x为根的这棵子树的儿子数(以x为根时已消失,因为x做根时不再是任何一个点的儿子)
n-siz[x]
再加上以fa为根的除x外另一棵树的儿子数,即所有的点减去以x为根的这棵子树的儿子数
2.另一种想法,每一条边(单向)经过该边时带来的贡献是一定的
(因为如果从相反方向过的话就走另一条边了。。。)
很巧妙的想法
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=2*100000+10;
int n;
struct Edge
{
int to,next;
}edge[maxn*2];
int first[maxn];
int cnt=0;
void add(int x,int y)
{
cnt++;
edge[cnt].next=first[x];
edge[cnt].to=y;
first[x]=cnt;
}
long long maxx=0;
long long ans[maxn];
long long siz[maxn];
void dfs1(int x,int fa)//算出每个点以1为根时儿子数及以1为根答案
{
siz[x]=1;
for(int i=first[x];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dfs1(v,x);
siz[x]+=siz[v];
}
ans[1]+=siz[x];
}
void dfs2(int x,int fa)//换根搜索
{
if(!ans[x])ans[x]=ans[fa]+n-siz[x]*2;
//printf("x%d %d\n",x,ans[x]);
maxx=max(ans[x],maxx);
for(int i=first[x];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dfs2(v,x);
}
}
int main()
{
scanf("%d",&n);
//printf("%d",n);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
/* for(int i=1;i<=n;i++)
printf("%d ",in[i]);
printf("\n");*/
dfs1(1,0);
//printf("1 %d\n",ans[1]);
dfs2(1,0);
printf("%lld",maxx);
return 0;
}