CF1856E2 题解
从题解区看来好像大家都觉得官方题解的思路是
思路
假设读者已经知道了 E1 的思路。
题目可以转化成对于每个点将它的子树内所有点黑白染色,使得颜色不同且不在同一个当前节点的儿子的子树内的对数最大,求最大的对数的总和。
首先有一个结论:存在一个最优解使得当前点的每个儿子子树内的节点颜色相同。
证明:一个儿子子树内的节点对答案的贡献只与子树外的点有关,因此这个子树内的点在外面状态不变的情况下染黑或白的贡献是固定的,所以节点颜色一定相同。
此时又可以发现,因为每个儿子子树内的节点颜色都相同,所以对对数的限制,“不在同一个当前节点的儿子的子树内”就没有用了,因此现在可以将问题转化成了:将每个儿子的子树大小划分成两个集合,使得集合元素之和的积最大。
现在可以考虑求出来所有第一个集合的可能元素之和。这是一个经典的背包问题。将这个问题形式化表述:
当前有
n 个物品,每个物品有重量a_i 。设m=\sum a_i ,请对于每个d \in [0,m] 求出是否可以选出一个集合S 使得\sum_{i|S} a_i = d 。
下面主要参考了 CF 上的这篇博客。
首先可以发现,不同的
因此现在将物品变成了有
接下来可以将
拆分后,用 bitset 优化 dp 即可。使用
这样做的复杂度看起来是
首先
那么
因此物品个数是
回到原问题,我们现在仅仅以
考虑如果有一个节点的一个儿子的子树大小占到了当前节点的子树大小一半,即
考虑计算递归树的第
对
代码
这题的一个细节是,对于每一个问题的 bitset 需要开动态的空间,但是 bitset 只能开静态的空间。有两种解决办法,一种是像下面给出的代码一样手写 bitset,另一种是使用神秘的 template,详见官方题解。
#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
#include<bits/stdc++.h>
using namespace std;
int n; vector<int> e[1000010];
int siz[1000010];
int cnt[1000010];
unsigned long long vis[(1000000>>6)+10];
void lshift(int n,int x)
{
if((x&63)==0)
{
for(int i=n; i>=(x>>6); --i) vis[i]|=vis[i-(x>>6)];
}
else
{
for(int i=n; i>=(x>>6); --i)
{
int wzl=(i<<6)-x;
vis[i]|=(wzl<0?0:vis[wzl>>6]>>(wzl&63))|(vis[(wzl>>6)+1]<<(64-(wzl&63)));
}
}
}
long long ans=0;
void dfs(int u)
{
siz[u]=1;
int ax=0;
for(int v:e[u]) dfs(v),siz[u]+=siz[v],ax=max(ax,siz[v]);
if(ax*2>=siz[u]-1) ans+=1ll*ax*(siz[u]-1-ax);
else
{
for(int v:e[u]) ++cnt[siz[v]];
memset(vis,0,((siz[u]-1>>6)+5)*8);
vis[0]=1;
for(int v:e[u])
{
if(cnt[siz[v]]==0) continue;
int now=1;
while(now<=cnt[siz[v]])
{
lshift(siz[u]-1>>6,siz[v]*now);
cnt[siz[v]]-=now,now*=2;
}
if(cnt[siz[v]]!=0) lshift(siz[u]-1>>6,siz[v]*cnt[siz[v]]);
cnt[siz[v]]=0;
}
int mid=(siz[u]-1)/2;
for(int i=0; ; ++i)
{
if(vis[mid+i>>6]>>(mid+i&63)&1) { ans+=1ll*(mid+i)*(siz[u]-1-mid-i); break; }
if(vis[mid-i>>6]>>(mid-i&63)&1) { ans+=1ll*(mid-i)*(siz[u]-1-mid+i); break; }
}
}
}
int main()
{
ios::sync_with_stdio(false),cin.tie(0);
cin>>n;
for(int i=2; i<=n; ++i)
{
int f; cin>>f;
e[f].push_back(i);
}
dfs(1);
cout<<ans;
return 0;
}