牛客多校训1 B Infinite Tree 虚树+数论+树状数组

9 篇文章 0 订阅
1 篇文章 0 订阅

第一场真的怀疑人生  做题做不动 补题补不动  还有叉姐的一句话题解 (看不懂啊呜呜呜)   

然后我左翻右翻 终于找到了这题的题解  博主讲的非常详细 : Infinite Tree  

知道有虚树这个东西 但是还没看过    虚树的建树过程大概就是  先把所有需要的点拿出来 显然这题我们需要的点 就是 1!,2!,3!,,,,,,,n!  这n个点   然后把他们按dfs序排序  所幸 这题按照1~n的顺序就是 dfs序排序的 我们找出每个点前面那个点的lca  最后套一个虚树的板子就可以建树了 

这些议事点是好处理的   我们画图就知道  i点的深度就是 i!包含的质因数的个数 (1!的深度为0)  我们用增量法可以完成建树  例如 2!建完后我们建立3!  显然再2!的基础上  多了3这个因子 那么我们把多的这个数进行质因数分解就可以  因此3!的深度就是 2!的深度+1    

关键是lca的深度怎么处理?   我们观察5!和6!   显然 他们分叉的地方在6的最大质因数的地方  那么5!和6!的lca的深度我们已经可以猜测出来了  其实就是  dep[lca(5!,6!)]=sum(6的最大质因数,n);  也就是说有全面所有大于等于6的最大质因数的 这些都会在lca到根节点的这条链上   之后就会开始分岔  计数可以用树状数组  可以看上面那个博主的图  多画一下就能理解

树建成以后考虑怎么去计算答案   首先我们统计以1为u的答案  显然就是 所有的 dp[i]*w[i]   (第i个点代表 i! 这个点) 

但是这不一定是最优答案  因为我们可以以其他的点为u   而其他点可以为u的充分必要条件是  w[1]-2*w[v]<0   

我们假设 1结点为u  而我们已经算出一个答案ans   

遍历1的所有子节点v  如果我们把u转移到v   那么处于1那边点到达v的距离会增加 dep[v]-dep[1]   处于v这一侧的点 的距离会减少 dep[v]-dep[1]  (类似换根dp) 而增加的总权值是 w[1]-w[v], 减少的总权值是 w[v]  所以 如果 w[1]-2*w[v]<0的话  我们会转移u结点 并且更新答案   因为 w[1]/2<w[v] 可知 满足这个条件的路径是唯一的  因此这样是可行的

  

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+100;
int c[N],n,tot,top,w[N],dep[N],lcadep[N],st[N];
typedef long long ll;
ll ans;
vector<int>g[N];
int mindiv[N];
void init(){
	ans=top=0;
	for(int i = 1; i <= tot; i++) 
	g[i].clear(),c[i]=w[i]=dep[i]=lcadep[i]=0;
}
void add(int x,int val){
	while(x<=n){
		c[x]+=val;
		x+=x&-x;
	}
}
int query(int x){
	int ret=0;
	while(x){
		ret+=c[x];
		x-=x&-x;
	}
	return ret;
}
void add_edge(int x,int y){
	g[x].push_back(y);g[y].push_back(x);
}
void sieve(int mx){
	for(int i = 2; i <= mx; i++){
		if(!mindiv[i])
		for(int j = i; j <= mx; j+=i)
		if(!mindiv[j]) mindiv[j]=i;
	}
}
void build(){
	st[top=1]=1;tot=n;
	for(int i = 2; i <= n; i++){
		dep[i]=dep[i-1]+1;int j = i;
		for( ; j != mindiv[j]; j /= mindiv[j]) dep[i]++;
		lcadep[i] = query(n)-query(j-1);
		for(j = i; j != 1; j /= mindiv[j]) add(mindiv[j],1);
	}
	for(int i = 2; i <= n; i++){
		while(top>1&&dep[st[top-1]]>=lcadep[i])
		add_edge(st[top-1],st[top]),top--;
		if(dep[st[top]]!=lcadep[i]){
			dep[++tot]=lcadep[i];
			add_edge(tot,st[top]);
			st[top]=tot;
		}
		st[++top]=i;
	} 
	while(top>1) add_edge(st[top-1],st[top]),top--;
}
void dfs1(int u,int f){
	ans+=1ll*dep[u]*w[u];
	for(auto v:g[u]){
		if(v!=f){
			dfs1(v,u);
			w[u]+=w[v]; 
		}
	}
}
void dfs2(int u,int f){
	for(auto v:g[u]){
		if(v!=f){
			if(w[1]-2*w[v]<0){
				ans+=1ll*(w[1]-2*w[v])*(dep[v]-dep[u]);
				dfs2(v,u); 
			}
		}
	}
}
int main(){
	sieve(1e5);
	while(~scanf("%d",&n)){
		init();
		for(int i = 1; i <= n; i++) scanf("%d",&w[i]);
		build();
		dfs1(1,0);dfs2(1,0);
		printf("%lld\n",ans);
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值