Codeforces Round 890 (Div. 2) E2. PermuTree (hard version)(根号分治+二进制优化多重背包+不定长bitset优化01背包)(nsqrt/w)

题目

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();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值