如题:
题目描述
很容易想到n^2的做法,
但是数据范围为
没法做,这时看整除这个条件,一看就让人想到分解质因子。
发现所有的值都为一个10^18以内的数的约数,
这个数的约数为10^5级别,
那么我们可以边dfs边枚举当前这个数的倍数(先将倍数离散化,用vector存整除关系),而非枚举父亲,
尽管这样看复杂度还是O(n^2)
但是枚举倍数就给了我们一个机会将状态分块
我们将1号点的值val[1],令val[1]=a*b (gcd(a,b)=1)
那么每一个点i ,存在val[i] = c*d ( gcd(c,d)=1 ) 使得 c|a , d|b
那么我们就从枚举val[i]的倍数,
变成了同时枚举c和d的倍数。(状态由一维变为两维)
但是这样时间复杂度还是不变。
回想一下,枚举倍数相当于DP中的填表法。
那我们用一下刷表法试试,将一个数对其因数的贡献存在一个数组里,用时直接O(1)取出。
但是这样时间复杂度还是不变。
那么神奇的事情发生了,
如果我对于第一维用刷表法,第二维用填表法,那时间复杂度是多少呢?
你会惊奇的发现它变为了O(n * (a的因数个数)+n * (b的因数个数)) ( a * b = val[1] )
当a和b的因数个数相近时,为O(n * sqrt(n))
可以换种方式理解,
这像是一个二维区域和的问题
填表法相当于你每次询问时都遍历所有的点求和,询问O(n^2),修改O(1)
刷表法相当于你每次修改时都更改一下所有你存储的二维前缀和,询问O(1),修改O(n^2);
一起用相当于每次修改时只更改一维前缀和,每次询问时查n个一维前缀和的的,询问O(n),修改O(n)。
可能很多时候大家都习惯于O((log(n))^2 )的树状数组,才导致这种方法比较冷门。
但例题求的不是区域和而是特定的几行几列(整除),这种方法才有用武之地。
当动态一维修改询问只能O(n)时,不如尝试一下将状态拆为两维,优化至O(sqrt(n))
代码:
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<vector>
#include<map>
#define mod 1000000007
#define maxn 200005
#define LL long long
using namespace std;
int n,l1,l2,Mid;
int info[maxn],Prev[maxn*2],to[maxn*2],cnt_e;
inline void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v;}
vector<int>G[2][1005];
template <class T> inline void read(T &res)
{
char ch;
while(!isdigit(ch=getchar()));
for(res=ch-'0';isdigit(ch=getchar());res=res*10+ch-'0');
}
LL val[maxn];
map<LL,int>Fac;
inline LL mul(LL a,LL b,LL c){ return (a*b-(LL)((long double)a/c*b+1e-10)*c)%c;}
inline LL gcd(LL a,LL b){ return !b ? a : gcd(b,a%b); }
inline LL power(LL base,LL k,LL P)
{
LL ret=1;
for(;k;k>>=1,base=mul(base,base,P)) if(k&1) ret=mul(ret,base,P);
return ret;
}
bool Miller_Rabin(LL n)
{
if(n==2) return 1;
LL res=n-1,k=0;
for(;!(res&1);res>>=1,k++);
for(int i=1;i<=6;i++)
{
LL now=((rand()<<16)+rand())%(n-2)+2,pre=power(now,res,n);
for(int j=1;j<=k;j++)
if((now=mul(pre,pre,n))==1 && pre!=1 && pre!=n-1) return 0;
else pre=now;
if(now!=1) return 0;
}
return 1;
}
LL Rho(LL n,LL c)
{
LL a=((rand()<<16)+rand())%(n-2)+2,b=a,d=1,k=1,i=1;
for(;d==1;k++)
{
d=gcd(abs((a=(mul(a,a,n)+c)%n)-b),n);
(k==i) && (b=a,i<<=1);
}
return (d==n) ? Rho(n,c+1) : d;
}
void Pollard(LL n)
{
if(Miller_Rabin(n))
{
Fac[n]++;
return;
}
LL d=Rho(n,3);
Pollard(d);
Pollard(n/d);
}
LL c[30],p[30],cnt_fac;
LL num[2][1005];
LL dp[maxn],had[1005][1005];
inline LL cut(LL a){ return a>=mod ? a-mod : a;}
void dfs(int now,int ff)
{
LL u=1,v;
for(int i=0;i<Mid;i++)
while(val[now]%p[i]==0)
u*=p[i],val[now]/=p[i];
u=lower_bound(num[0],num[0]+l1,u)-num[0],v=lower_bound(num[1],num[1]+l2,val[now])-num[1];
for(vector<int>::iterator it=G[1][v].begin();it!=G[1][v].end();it++)
dp[now]=cut(dp[now]+had[u][*it]);
for(vector<int>::iterator it=G[0][u].begin();it!=G[0][u].end();it++)
had[*it][v]=cut(had[*it][v]+dp[now]);
for(int i=info[now];i;i=Prev[i])
if(to[i]!=ff)
dfs(to[i],now);
for(vector<int>::iterator it=G[0][u].begin();it!=G[0][u].end();it++)
had[*it][v]=cut(had[*it][v]-dp[now]+mod);
}
int main()
{
/*
freopen("walk.in","r",stdin);
freopen("walk.out","w",stdout);
*/
scanf("%d",&n);
for(int u,v,i=1;i<n;i++)
{
read(u),read(v);
Node(u,v),Node(v,u);
}
for(int i=1;i<=n;i++) read(val[i]);
Pollard(val[1]);
for(map<LL,int>::iterator it=Fac.begin();it!=Fac.end();it++)
p[cnt_fac]=(*it).first,c[cnt_fac++]=(*it).second;
int Min=1<<30,Loc;
for(int sta=0;sta<1<<cnt_fac;sta++)
{
int siz[2]={1,1};
for(int i=0;i<cnt_fac;i++)
if(sta>>i & 1) siz[1]*=c[i]+1;
else siz[0]*=c[i]+1;
if(max(siz[0],siz[1])<Min) Min=max(siz[0],siz[1]),l1=siz[1],l2=siz[0],Loc=sta;
}
for(int i=0;i<cnt_fac;i++)
if(Loc>>i & 1)
c[i]*=-1,Mid++;
for(int l=0,r=cnt_fac-1;l<r;l++)
if(c[l]>0)
swap(c[l],c[r]),swap(p[l--],p[r--]);
for(int i=0;i<Mid;i++) c[i]*=-1;
for(int i=0;i<l1;i++)
{
int tmp=i;num[0][i]=1;
for(int j=0;j<Mid;tmp/=c[j++]+1)
for(;tmp%(c[j]+1);) tmp--,num[0][i]*=p[j];
}
sort(num[0],num[0]+l1);
for(int i=0;i<l1;i++)
for(int j=0;j<l1;j++)
if(num[0][i]%num[0][j]==0)
G[0][i].push_back(j);
for(int i=0;i<l2;i++)
{
int tmp=i;num[1][i]=1;
for(int j=Mid;j<cnt_fac;tmp/=c[j++]+1)
for(;tmp%(c[j]+1);) tmp--,num[1][i]*=p[j];
}
sort(num[1],num[1]+l2);
for(int i=0;i<l2;i++)
for(int j=0;j<l2;j++)
if(num[1][j]%num[1][i]==0)
G[1][i].push_back(j);
dp[1]=1;
dfs(1,0);
for(int i=1;i<=n;i++)
printf("%lld\n",dp[i]);
}