【计蒜之道2019初赛1 BCD】【计蒜客39263】商汤AI园区的n个路口 题解

题目大意

  有一棵 nn 个点的树,每条边有边权,边权互不相同,范围为 [1,m][1,m]
  现在你要给每个点定一个点权,点权范围也是 [1,m][1,m]
  假设一条边连着 aabb,边权为 ww,那么点权 vav_avbv_b 要满足 gcd(va,vb)w\gcd(v_a,v_b)\not = w
  求方案数。

  medium:
  1nm10001 \leq n \leq m \leq 1000
  hard:
  1nm1051 \leq n \leq m \leq 10^5

  这个题挺有趣的。

medium

  首先列出一个 dp:设 fi,jf_{i,j} 表示第 ii 个点,点权为 jj 的方案数,记 si=j=1mfi,js_i=\sum_{j=1}^m f_{i,j},那么转移很简单:

fi,j=sonk=1m[gcd(j,k)w]fson,k=son(ssonk=1m[gcd(j,k)=w]fson,k)\begin{aligned} f_{i,j}&=\prod_{son}\sum_{k=1}^m [\gcd(j,k)\not=w]f_{son,k} \\ &=\prod_{son}(s_{son}-\sum_{k=1}^m [\gcd(j,k)=w]f_{son,k}) \end{aligned}

  计算减去 [gcd(j,k)=w]fson,k[\gcd(j,k)=w]f_{son,k} 这一部分的时候,实际上 jjkk 都是 ww 的倍数。由于边权互不相同,因此时间最多为

w=1mmw2(w=1mmw)2(mlnm)2\begin{aligned} &\sum_{w=1}^m \lfloor \frac mw \rfloor^2 \\ \leq &\bigg(\sum_{w=1}^m \lfloor \frac mw \rfloor \bigg)^2 \\ \leq &(m\ln m)^2 \end{aligned}

  因此总的时间为 O(nm+m2ln2m)O(nm+m^2\ln^2 m),这就可以过掉 medium 了。

hard

  考虑优化这个 dp。可以发现,大部分的 fi,jf_{i,j} 都等于 sonsson\prod_{son}s_{son},只有 mlnmm \ln mfi,jf_{i,j} 是特殊的。所以只要快速计算出这些特殊位置就行了。
  即,对于 ii 的儿子 sonson(连过去的边的边权为 ww),当 jjww 的倍数时,快速得到

illegalj=k=1m[gcd(j,k)=w]fson,killegal_j=\sum_{k=1}^m [\gcd(j,k)=w]f_{son,k}

  莫比乌斯反演一下:

k=1m[gcd(j,k)=w]fson,k=k=1mw[gcd(jw,k)=1]fson,kw=djwμ(d)k=1mwdfson,kwd\begin{aligned} &\sum_{k=1}^m [\gcd(j,k)=w]f_{son,k} \\ =& \sum_{k=1}^{\lfloor \frac mw \rfloor} [\gcd(\frac jw,k)=1] f_{son,kw} \\ =& \sum_{d|\frac jw} \mu(d) \sum_{k=1}^{\lfloor \frac{m}{wd} \rfloor} f_{son,kwd} \end{aligned}

  对于 sonson,枚举 dd,然后枚举 kk 算出 μ(d)k=1mwdfson,kwd\mu(d) \sum_{k=1}^{\lfloor \frac{m}{wd} \rfloor} f_{son,kwd},最后枚举 jj 把这个 dd 的贡献加到那个 jj 里就好了。

  来算一下复杂度。dd 的范围是 [1,mw][1,\lfloor \frac mw \rfloor],在枚举了 dd 以后枚举 kk 的时间就是

d=1mwmwd=O(mwlnmw)\sum_{d=1}^{\lfloor \frac mw \rfloor} \lfloor \frac{m}{wd} \rfloor=O(\frac mw \ln \frac mw)

  枚举 jj 同理,算上全部的 ww 的话就是

w=1mO(mwlnmw)=O(lnm)w=1mO(mw)=O(lnm)O(mlnm)=O(mln2m)\begin{aligned} &\sum_{w=1}^m O(\frac mw \ln \frac mw) \\ =& O(\ln m) \cdot \sum_{w=1}^m O(\frac mw) \\ =& O(\ln m) \cdot O(m \ln m) \\ =& O(m \ln^2 m) \end{aligned}

  因此总时间是 O(n+mln2m)O(n+m\ln^2m)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

typedef long long LL;

const int maxn=1e5+5;
const LL mo=1e9+7;

int n,m;

int p0,p[maxn],mu[maxn];
bool bz[maxn];
void Pre(int n)
{
mu[1]=1;
fo(i,2,n)
{
if (!bz[i]) p[++p0]=i, mu[i]=-1;
fo(j,1,p0)
{
if ((LL)i*p[j]>n) break;
bz[i*p[j]]=1;
if (i%p[j]==0) break;
else mu[i*p[j]]=-mu[i];
}
}
}

int tot,go[2*maxn],val[2*maxn],nxt[2*maxn],f1[maxn];
void ins(int x,int y,int z)
{
go[++tot]=y;
val[tot]=z;
nxt[tot]=f1[x];
f1[x]=tot;
}

LL mi(LL x,LL y)
{
LL re=1;
for(; y; y>>=1, x=x*x%mo) if (y&1) re=re*x%mo;
return re;
}

map<int,LL> f[maxn];
LL s[maxn],ilg[maxn];
LL findF(int k,int j) { return (f[k].count(j)) ?f[k][j] :f[k][0] ; }
void dfs(int k,int last)
{
LL pro=1;
for(int p=f1[k]; p; p=nxt[p]) if (go[p]!=last)
{
dfs(go[p],k);

(pro*=s[go[p]])%=mo;
for(int d=val[p], di=1; d<=m; d+=val[p], di++)
{
LL sum=0;
for(int y=d; y<=m; y+=d) (sum+=mu[di]*findF(go[p],y))%=mo;
for(int x=d; x<=m; x+=d) (ilg[x/val[p]]+=sum)%=mo;
}
LL ny=mi(s[go[p]],mo-2);
for(int j=val[p]; j<=m; j+=val[p])
if (f[k].count(j)) (f[k][j]*=(s[go[p]]-ilg[j/val[p]])*ny%mo)%=mo;
else f[k][j]=(s[go[p]]-ilg[j/val[p]])*ny%mo;
fo(j,1,m/val[p]) ilg[j]=0;
}
s[k]=pro*m%mo;
for(auto p:f[k])
{
int j=p.first;
(f[k][j]*=pro)%=mo;
s[k]=(s[k]-pro+f[k][j])%mo;
}
f[k][0]=pro;
}

int main()
{
Pre(maxn-5);

scanf("%d %d",&n,&m);
fo(i,1,n-1)
{
int x,y,w;
scanf("%d %d %d",&x,&y,&w);
ins(x,y,w), ins(y,x,w);
}

dfs(1,0);

printf("%lld\n",(s[1]+mo)%mo);
}