题目链接:点击查看
题目大意:给出一个无穷个节点的树,对于每个大于 1 的点 i 来说,可以向点 i / minvid[ i ] 连边,这里的 mindiv[ x ] 表示的是 x 的最小质因数,现在给定 m 个点分别是 1! , 2! , 3! ... ,每个点都有个权值 w[ i ] ,现在需要找出一个点 u ,使得最小,输出这个最小值
题目分析:先说收获,通过这个题让我稍微明白了一点换根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
读完上面的博客后,我们已经知道,如果想要构造出虚树,必须要知道的是:
- m 个节点的 dfn ( dfs序 )
- m 个节点排好序后,所有相邻节点的 lca 的 dfn
所以此时的关键信息就是需要知道,m 个节点的 dfn,下面借用一下zx学长的图片:
如图就是含有 1! , 2! , 3! , 4! , 5! , 6! 的一棵树,省略了很多无用节点
可以通过观察出的一些结论就是:
- 对于每一个 i 来说,点 1 到点 i! 的链的长度是与 i 呈正相关的关系
- 还是上面提到的链,质因子的大小不增,例如 1 ~ 5! 这条链的边权依次为:5 3 2 2 2
- 将结论 1 具化一下就是 d[ i + 1 ] = d[ i ] + ( i + 1 的质因子个数 )
- 对于 1 ~ i! 这条链来说,一定是由 1 ~ ( i - 1 )! 这条链加上 i 的质因子继承过来的,例如 1 ~ 3! 的边权依次为:3 2,而 1 ~ 4! 的边权依次为 3 2 2 2,因为 4 的质因子为 2 * 2 ,所以 1 ~ 4! 的边权在 1 ~ 3! 的基础上增加了两个 2
- 按照上图构造的树,点 i! 和点 i 的 dfn 是呈正相关的关系
- 假如唯一分解 的形式,那么 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;
}