题目
n(n<=1e6)的一棵树,将[1,n]的某排列p分配到树上每个点的点权,
计点i的点权是ai,最大化(u,v)点对的数量,使得a[u]<a[lca(u,v)]<a[v],输出这个最大的值
思路来源
hanasaki竹子代码
心得
学了下开不定长的bitset的写法,可以通过模板函数递归的方式,
因为模板函数的参数是个const,但又支持传入这个参数
不断翻倍,就可以找到超过且和当前tot大小最接近的bitset的大小
template<int LEN>void solve(){
if(LEN<=tot){solve<min(N,LEN<<1)>();return;}}
solve<1>();
题解
每个子树u的直连儿子v,每个v有一个size,
在对lca决策的时候,将一部分放比lca小的值,另一部分放比lca大的值,
这样贡献就是小的个数*大的个数,相当于做一个背包,使得二者越接近越好
计u的子树size总和为tot,如果存在一个子树的size超过tot的一半,显然可以直接算
否则,每个子树的size都不超过一半,递归log层就到底了,
所以遍历[0,tot]找答案是可行的,这部分复杂度O(nlogn)
根号分治
将sz不超过sqrt(n)的物品计在数组里,
遍历小于根号的,从底往上推,
每个物品至少留一个,其余剩下的都可以推到二倍
如果子树size超过sqrt了,由于整棵树不超过n,所以个数不超过sqrt个
将这sqrt个物品,直接用bitset优化背包转移
这部分复杂度O(n*sqrt(n)/w),w为bitset位长,一般取32或64
补充说明
如果对小于sqrt的每个物品暴力做二进制拆位优化多重背包,
会导致复杂度会多一个log(sqrt(n)),但也比较小,本题允许通过
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1000013;
typedef long long ll;
vector<int>mp[N];
int p[N],sz[N];
ll ans=0;
vector<int>sizes;
int tot=0;
int c[1010];
template<int LEN>void solve(){
if(LEN<=tot){solve<min(N,LEN<<1)>();return;}
bitset<LEN>B;B[0]=1;
const int SB=(int)sqrtl(tot);
vector<int>size2;
for(auto s:sizes)
if(s<SB)c[s]++;
else size2.push_back(s);
for(int i=1;i<SB;i++)if(c[i]){
int w=(c[i]-1)/2;
if(i*2>=SB){
for(int j=1;j<=w;++j){
size2.push_back(i*2);
}
}
else{
c[i*2]+=w;
}
c[i]=1+(c[i]-1)%2;
for(int j=1;j<=c[i];++j)size2.push_back(i);
c[i]=0;
}
for(auto s:size2)B|=B<<s;
ll mx=0;
for(int i=0;i<=tot;i++)if(B[i])mx=max(mx,(ll)(tot-i)*i);
ans+=mx;
}
void dfs(int x,int fa){
sz[x]=1;
for(auto i:mp[x])if(i^fa)dfs(i,x),sz[x]+=sz[i];
for(auto i:mp[x])if(i^fa)sizes.push_back(sz[i]);
tot=sz[x]-1;
for(auto s:sizes)if(s>=tot/2){
ans+=(ll)s*(tot-s);sizes.clear();
return;
}
solve<1>(), sizes.clear();
}
void solve(){
int n=1000000;
cin>>n;
for(int i=2;i<=n;i++){
cin>>p[i];
mp[p[i]].push_back(i);
}
dfs(1,0);
cout<<ans<<'\n';
}
signed main() {
cin.tie(nullptr);ios::sync_with_stdio(false);
int T=1;
//cin>>T;
while(T--)solve();
}