题目链接
题意
给定一棵 n 个点的树,初始全是白点。
要求你做 n 步操作,每一次选定一个与一个黑点有连边的白点,将它染成黑点,然后获得该白点被染色前所在的白色联通块大小的权值(第一次操作可以任意选点)。
求可获得的最大权值。
思路
确定完第一个点,可获得的权值就已经确定。
记 fi 为先染 1 时,i 的子树对答案的贡献。
易得到递推式:
fi=sizi+j∈soni∑fj
记 resi 为先染 i 时整棵树可获得的权值,对整棵树进行换根 DP。
如图,假设我们要将根从 x 换到 y,如何转移?
resxresy=n+i∈sonx∑fi=n+(n−sizy)+i∈sonx∣i=y∑fi+j∈sony∑fj=n+(n−sizy)+i∈sonx∣i=y∑fi+(fy−sizy)=(n+i∈sonx∑fi)−sizy+(n−sizy)=resx+n−2⋅sizy
即可实现 O(1) 转移。
实现
分两次 dfs,第一次求出 f,第二次求出 res。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include<cstdio> using namespace std; int n,tot,fir[200001],fa[200001],siz[200001]; long long f[200001],res[200001],ans; struct edge { int to; int nex; }e[399999]; inline long long max(long long x,long long y) { return x>=y?x:y; } inline void add(int u,int v) { e[++tot]=edge{v,fir[u]}; fir[u]=tot; return; } void dfs(int u) { siz[u]=1; for(int i=fir[u],v;v=e[i].to,i;i=e[i].nex) { if(v==fa[u]) { continue; } fa[v]=u; dfs(v); siz[u]+=siz[v],f[u]+=f[v]; } f[u]+=siz[u]; return; } void solve(int u) { if(u!=1) { res[u]=res[fa[u]]+n-(siz[u]<<1); ans=max(ans,res[u]); } for(int i=fir[u],v;v=e[i].to,i;i=e[i].nex) { if(v!=fa[u]) { solve(v); } } return; } int main() { scanf("%d",&n); for(int i=1,u,v;i<n;++i) { scanf("%d%d",&u,&v); add(u,v),add(v,u); } dfs(1); ans=res[1]=f[1]; solve(1); printf("%lld\n",ans); return 0; }
|