牛客多校1 - Infinite Tree(虚树+换根dp+树状数组)

题目链接:点击查看

题目大意:给出一个无穷个节点的树,对于每个大于 1 的点 i 来说,可以向点 i / minvid[ i ] 连边,这里的 mindiv[ x ] 表示的是 x 的最小质因数,现在给定 m 个点分别是 1! , 2! , 3! ... ,每个点都有个权值 w[ i ] ,现在需要找出一个点 u ,使得\sum_{i=1}^{m}w_i*dist(u,i!)最小,输出这个最小值

题目分析:先说收获,通过这个题让我稍微明白了一点换根dp。。话说为什么不直接去刷换根dp的题目,感觉有点多此亿举

再说简单的部分,如果 m 个节点的树已经求出来了,猜也能猜出来点 u 一定是 m 个点中的一个点,所以我们可以通过树形dp的换根维护一下最小值就能得到答案了,具体操作如下:

设 dp1[ i ] 为以点 i 为子树时的点权之和,转移方程为:dp1[ u ] += dp1[ v ],换根时的转移方程为:dp1[ u ] -= dp1[ v ] , dp1[ v ] += dp1[ u ]

然后设 dp2[ i ] 为以点 i 为子树是题目中公式的结果,因为子树的权值和已经通过 dp1 转移好了,现在只需要结合距离,也就是深度差就可以转移 dp2 了,可以想象为 u 的所有子节点 v 所代表的子树,需要沿着 u - v 这条路径向上转移,也就是 dp2[ u ] += dp2[ v ] + ( deep[ v ] - deep[ u ] ) * dp1[ v ] ,当然换根时的转移方程也是大同小异:dp2[ u ] -= dp2[ v ] + ( deep[ v ] - deep[ u ] ) * dp1[ v ] ,dp2[ v ] += dp2[ u ] + ( deep[ v ] - deep[ u ] ) * dp1[ u ]

注意一下换根时的先后顺序,应该先在当前节点 u 中减去子节点 v 的贡献,然后再在 v 中加上 u 的贡献

转移完成后,dp2[ root ] 就是当 u 选为 root 时的答案,为了方便起见,将 root 设为 1 即可

然后就是比较难的虚树部分了,因为涉及到了阶乘,所以原图中节点的数量会非常庞大,但经过上面的分析,大量的节点都是无用点,只有 1!,2!,3! ... i! ... m! 最多 m 个节点是有用的节点,这样不难想到虚树,但因为无法建立原图,所以不能按照常规的方式构造虚树,这里需要更加深入了解一下虚树的构造方法:https://www.cnblogs.com/zwfymqz/p/9175152.html

读完上面的博客后,我们已经知道,如果想要构造出虚树,必须要知道的是:

  1. m 个节点的 dfn ( dfs序 )
  2. m 个节点排好序后,所有相邻节点的 lca 的 dfn

所以此时的关键信息就是需要知道,m 个节点的 dfn,下面借用一下zx学长的图片:

在这里插入图片描述

如图就是含有 1! , 2! , 3! , 4! , 5! , 6! 的一棵树,省略了很多无用节点

可以通过观察出的一些结论就是:

  1. 对于每一个 i 来说,点 1 到点 i! 的链的长度是与 i 呈正相关的关系
  2. 还是上面提到的链,质因子的大小不增,例如 1 ~ 5! 这条链的边权依次为:5 3 2 2 2
  3. 将结论 1 具化一下就是 d[ i + 1 ] = d[ i ] + ( i + 1 的质因子个数 )
  4. 对于 1 ~ i! 这条链来说,一定是由 1 ~ ( i - 1 )! 这条链加上 i 的质因子继承过来的,例如 1 ~ 3! 的边权依次为:3 2,而 1 ~ 4! 的边权依次为 3 2 2 2,因为 4 的质因子为 2 * 2 ,所以 1 ~ 4! 的边权在 1 ~ 3! 的基础上增加了两个 2
  5. 按照上图构造的树,点 i! 和点 i 的 dfn 是呈正相关的关系
  6. 假如唯一分解 i!=p_1^{e1}p_2^{e2}p_3^{e3}...p_k^{ek}的形式,那么 dis( 1 , i! ) = e1 + e2 + e3 + ... + ek

现在问题就剩下了,如何求两个点相邻节点 ( ( i - 1 )! , i! ) 的 lca 的 dfn

上面的第四个结论提到了,1 ~ i! 这条链是从 1 ~ ( i - 1 )! 这条链继承而来的,再结合第二个结论,得出 lca( ( i - 1 )! , i! ) 一定是由( i - 1 )! 和 i! 前面的最大公共质因子组成的一条链,而 1 ~ i! 这条链相对于 1 ~ ( i - 1 )! 这条链多出了 i 的质因子条边,所以只需要在 1 ~ ( i - 1 )! 这条链上找到 i 的最大质因子的位置即可,这样就能根据第六个结论计算出 dfn[ lca ] 了,可以用线段树或树状数组来实现这个功能,只需要在迭代的同时,利用数据结构维护一下当前 i! 这条链上每个质因子的出现次数即可

注意在这个题目中, dfn 和 deep 我都用了 dfn 数组表示,然后 dfn[ i ] 表示的是点 i 的 dfn,而 dfn[ i + n ] 代表的是 lca( ( i - 1 )! , i! ) 的 dfn

代码:
 

#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
  
typedef long long LL;
  
typedef unsigned long long ull;
  
const int inf=0x3f3f3f3f;

const int N=1e6+100;

int n,w[N],mindiv[N],c[N],dfn[N],st[N],top;

LL dp1[N],dp2[N],ans;

vector<int>node[N];

int lowbit(int x)
{
	return x&(-x);
}

void add(int pos)
{
	while(pos<=n)
	{
		c[pos]++;
		pos+=lowbit(pos);
	}
}

int ask(int pos)
{
	int ans=0;
	while(pos)
	{
		ans+=c[pos];
		pos-=lowbit(pos);
	}
	return ans;
}

void build()
{
	dfn[1]=1;
	for(int i=2;i<=n;i++)
	{
		dfn[i]=dfn[i-1];
		int j=i;
		while(j!=mindiv[j])//求出i的最大质因子 
			j/=mindiv[j];
		dfn[i+n]=ask(n)-ask(j-1)+1;//dfn[i+n]=dfn[lca((i-1)!,i!)]
		j=i;
		while(j!=1)//更新树状数组维护的链 
		{
			dfn[i]++;
			add(mindiv[j]);
			j/=mindiv[j];
		}
	}
	int top=0;
	st[top]=1;
	for(int i=2;i<=n;i++)
	{
		int x=i,y=i+n;
		while(top&&dfn[st[top-1]]>=dfn[y])
		{
			node[st[top-1]].push_back(st[top]);
			top--;
		}
		if(dfn[y]!=dfn[st[top]])
		{
			node[y].push_back(st[top]);
			st[top]=y;
		}
	    st[++top]=x;
	}
	while(top)
	{
		node[st[top-1]].push_back(st[top]);
		top--;
	}
}

void dfs1(int u)
{
	dp1[u]=w[u];
	for(auto v:node[u])
	{
		dfs1(v);
		dp1[u]+=dp1[v];
		dp2[u]+=dp2[v]+dp1[v]*(dfn[v]-dfn[u]);
	}
}

void dfs2(int u)
{
	ans=min(ans,dp2[u]);
	for(auto v:node[u])
	{
		LL u1=dp1[u],u2=dp2[u],v1=dp1[v],v2=dp2[v];
		dp2[u]-=dp2[v]+dp1[v]*(dfn[v]-dfn[u]);
		dp1[u]-=dp1[v];
		dp1[v]+=dp1[u];
		dp2[v]+=dp2[u]+dp1[u]*(dfn[v]-dfn[u]);
		dfs2(v);
		dp1[u]=u1,dp2[u]=u2,dp1[v]=v1,dp2[v]=v2;
	}
}

void init(int n)
{
	ans=1e18;
	for(int i=0;i<=n<<1;i++)
	{
		w[i]=c[i]=dp1[i]=dp2[i]=0;
		node[i].clear();
	}
}

void init()//预处理mindiv[x]:x的最小质因子 
{
	mindiv[1]=1;
	for(int i=2;i<N;i++)
		if(!mindiv[i])
			for(int j=i;j<N;j+=i)
				if(!mindiv[j])
					mindiv[j]=i;
}

int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
	init();
	while(scanf("%d",&n)!=EOF)
	{
		init((n<<1)+5);
		for(int i=1;i<=n;i++)
			scanf("%d",w+i);
		build();//建虚树
		dfs1(1);
		dfs2(1);
		printf("%lld\n",ans);
	}










    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/108266962
今日推荐