【题目】
原题地址
给一棵树,每个节点有一个权值
val
v
a
l
。
如果两个点
a
a
和满足
a
a
为的祖先且
val[b]
v
a
l
[
b
]
为
val[a]
v
a
l
[
a
]
的约数,那么可以从
a
a
一步跳到。
求从1号节点走到各每个节点的路径数。
n<=105,val[i]<=1018
n
<=
10
5
,
v
a
l
[
i
]
<=
10
18
,保证对于任意节点
i
i
,为
val[1]
v
a
l
[
1
]
的约数。
【题目分析】
woc这个权值这么大,又要打Miller_Rabin&Pollard_Rho了,心态爆炸。
【解题思路】
1018的约数
10
18
的
约
数
大约有
105
10
5
个。
一个显然的暴力是:对
val[1]
v
a
l
[
1
]
分解质因数,求出其所有约数,用一个数组记录每个约数。dfs一遍这棵树,对于每个节点,枚举
val[1]
v
a
l
[
1
]
的所有约数看是否是自己的倍数,并对
f
f
进行贡献,然后再把这个点的贡献到数组中。
约数启发我们在质因数方面下手,一个显然的结论是:一个数
A
A
是另一个数的约数,充要条件是
A
A
的所有种类质因子数对应质因子数。
因此我们可以将每一个数看成一个[约数]维的向量,接下来我们要处理的其实就是一个高维前缀和。
如何理解高维前缀和?我们先从二维开始考虑。
假设一个数为
2x∗3y
2
x
∗
3
y
,那么用向量
(x,y)
(
x
,
y
)
表示,在二维矩阵中,如果要查询比它小的向量可以采用下面的方法:
1:单点修改
O(1)
O
(
1
)
,矩形查询左上角向量值之和
O(n∗m)
O
(
n
∗
m
)
2:矩形修改右下角所有向量值
O(n∗m)
O
(
n
∗
m
)
,单点查询
O(1)
O
(
1
)
两种都不能满足我们的要求,这启发我们均摊复杂度。
可以考虑下面一种方法:对于每一个向量
(x,y)
(
x
,
y
)
,我们前缀和记录所有
x
x
和它一样,小于等于它的向量值之和。这样对于每次查询,我们只需要固定
y
y
,扫描;对于修改,我们固定
x
x
,扫描。就像下面这样。
考虑拓展到高维,我们发现每一维修改查询时间和向量最大值有关,启发我们将向量分成两个尽可能相等的组。如果一个数分解为 ap11∗ap22∗…∗apxx a 1 p 1 ∗ a 2 p 2 ∗ … ∗ a x p x ,那么每一维度的大小应该分成 p1∗p2∗...∗px−−−−−−−−−−−√ p 1 ∗ p 2 ∗ . . . ∗ p x ,这个数大概是 103 10 3 ,分块以后下标的处理就是将这块里的 pi+1 p i + 1 都乘起来什么的。
这样查询和修改复杂度就是
103
10
3
,再乘上点数时间复杂度就
O(1e8)
O
(
1
e
8
)
了,需要卡常。当然如果有偏重的话两个块大小可能会不平衡,这时就需要更玄学的卡常了,也许我们可以再写一个程序均等分配。
【参考代码】
#include<bits/stdc++.h>
#define pb(x) push_back(x)
using namespace std;
typedef long long LL;
const int N=1e5+10;
const int M=5005;
const int S=15;
const int mod=1e9+7;
int n,tot,head[N],fa[N];
LL val[N],f[N];
LL read()
{
LL ret=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
while(isdigit(c)) {ret=ret*10ll+(c^48);c=getchar();}
return f?ret:-ret;
}
void write(LL x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar((x%10)^48);
}
struct Tway
{
int v,nex;
};
Tway e[N<<1];
void add(int u,int v)
{
e[++tot]=(Tway){v,head[u]};head[u]=tot;
e[++tot]=(Tway){u,head[v]};head[v]=tot;
}
void up(LL &x,LL y)
{
x+=y;
if(x>mod) x-=mod;
}
struct PRIME
{
LL top,stk[S*10],cnt[S*10];
LL gcd(LL x,LL y)
{
if(x==0) return 1;
if(x<0) return gcd(-x,y);
while(y) {LL t=x%y;x=y;y=t;}
return x;
}
LL mult_mod(LL a,LL b,LL c)
{
a%=c;b%=c;
LL ret=0;
while(b)
{
if(b&1) {ret+=a;ret%=c;}
a<<=1;b>>=1;
if(a>=c) a%=c;
}
return ret;
}
LL pow_mod(LL x,LL y,LL mod)
{
if(y==1)
return x%mod;
x%=mod;LL tmp=x,ret=1;
while(y)
{
if(y&1)
ret=mult_mod(ret,tmp,mod);
tmp=mult_mod(tmp,tmp,mod);
y>>=1;
}
return ret;
}
bool check(LL a,LL n,LL x,LL t)
{
LL ret=pow_mod(a,x,n);
LL last=ret;
for(LL i=1;i<=t;i++)
{
ret=mult_mod(ret,ret,n);
if(ret==1 && last!=1 && last!=n-1)
return true;
last=ret;
}
if(ret!=1) return true;
return false;
}
bool Miller_Rabin(LL n)
{
if(n<2) return false;
if(n==2) return true;
if(!(n&1)) return false;
LL x=n-1,t=0;
while(!(x&1)) {x>>=1;t++;}
for(LL i=0;i<S;i++)
{
LL a=rand()%(n-1)+1;
if(check(a,n,x,t))
return false;
}
return true;
}
LL Pollard_rho(LL x,LL c)
{
LL i=1,k=2,x0=rand()%x,y=x0;
while(true)
{
i++;
x0=(mult_mod(x0,x0,x)+c)%x;
LL d=gcd(y-x0,x);
if(d!=1 && d!=x) return d;
if(y==x0) return x;
if(i==k) {y=x0;k+=k;}
}
}
void findfac(LL n)
{
if(Miller_Rabin(n))
{
stk[++top]=n;cnt[top]=0;
return;
}
LL p=n;
while(p>=n)
p=Pollard_rho(p,rand()%(n-1)+1);
findfac(p);findfac(n/p);
}
void work(LL x)
{
top=0;findfac(x);int t=top;
sort(stk+1,stk+top+1);
top=0;stk[0]=-1;
for(int i=1;i<=t;++i)
{
if(stk[i]^stk[top])
stk[++top]=stk[i],cnt[top]=0;
cnt[top]++;
}
}
};
struct Divide
{
int mid;
PRIME pi;
LL sum[M][M];
void init(LL x)
{
pi.work(x);
mid=pi.top>>1;
}
void modify(int x,int idl,int idr,LL v,int p)
{
if(p==mid+1)
{
up(sum[idl][idr],v);
return;
}
int cs=0;
while(x/pi.stk[p]*pi.stk[p]==x)
x/=pi.stk[p],++cs;
for(int i=cs;~i;--i)
modify(x,idl*(pi.cnt[p]+1)+i,idr,v,p+1);
}
void update(LL x,LL v)
{
int idr=0;
for(int i=mid+1;i<=pi.top;++i)
{
int cs=0;
while(x/pi.stk[i]*pi.stk[i]==x)
x/=pi.stk[i],++cs;
idr=idr*(pi.cnt[i]+1)+cs;
}
modify(x,0,idr,v,1);
}
LL query(LL x,int idl,int idr,int p)
{
if(p==pi.top+1)
return sum[idr][idl];
int cs=0;LL ret=0;
while(x/pi.stk[p]*pi.stk[p]==x)
x/=pi.stk[p],++cs;
for(int i=cs;i<=pi.cnt[p];++i)
up(ret,query(x,idl*(pi.cnt[p]+1)+i,idr,p+1));
return ret;
}
LL getans(LL x)
{
int idr=0;
for(int i=1;i<=mid;++i)
{
int cs=0;
while(x/pi.stk[i]*pi.stk[i]==x)
x/=pi.stk[i],++cs;
idr=idr*(pi.cnt[i]+1)+cs;
}
return query(x,0,idr,mid+1);
}
}A;
void dfs(int x)
{
if(x^1)
f[x]=A.getans(val[x]);
A.update(val[x],f[x]);
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v^fa[x])
fa[v]=x,dfs(v);
}
A.update(val[x],mod-f[x]);
}
int main()
{
freopen("walk.in","r",stdin);
freopen("walk.out","w",stdout);
srand(19260817);
n=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
add(u,v);
}
for(int i=1;i<=n;++i)
val[i]=read();
A.init(val[1]);
f[1]=1;dfs(1);
for(int i=1;i<=n;++i,putchar('\n'))
write(f[i]);
return 0;
}
【总结】
高维前缀和是一个好东西,分块优化复杂度也是一个很好的思想(其实是均摊复杂度啦)。