题意:
给一棵树,每条边有一边权,取值范围是[0,9],定义两点间的路径长度是这两点路径上边权一个个连起来组成的数字,问,这棵树中路径长≡ 0(mod M)的路径有几条,保证gcd(10,M) = 1
solution:
询问路径联系到点分治,那么假设我们现在处理以x为根的这棵子树,对于这棵子树中的每个点i,维护A[i]为路径(i,x)的长度,维护B[i]为路径(x,i)的长度,假设路径(i,j)为一条x子树中经过x的路径,如果这条路径对答案有贡献,则有A[i]*10^len[j] + B[j] ≡ 0(mod M) 此处len[j]为路径(x,j)的长度。
对同余式稍作变换,得到B[j]*(10^len[j])^(-1) ≡ M - A[i](mod M)。
为什么这么做呢?因为题目保证gcd(10,M) = 1,那么10^k在模M的剩余系下逆元必定存在
现在只需要把M - A[i]存入一个hash表,每次查询个数就行了,(子树的搜索需要正序一次逆序一次)
以及,,特判A[i]与B[i]本来就是0的路径,,
此题不适用费马小定理求逆元,因为M不是素数,不满足其成立条件,所以使用扩展欧几里得算法--
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 1E5 + 10;
typedef unsigned long long UL;
typedef long long LL;
const int h = 1E7 + 7;
const LL ten = 10;
const int INF = ~0U>>1;
struct E{
int to,w;
E(){}
E(int to,int w): to(to),w(w){}
};
int n,cnt,O,Max,siz[maxn],ti[h],Num[h],vis[h];
bool bo[maxn];
LL Ans,M,A[maxn],B[maxn],L[maxn],inv[maxn],mi[maxn];
vector <E> v[maxn];
void Dfs2(int x,int tot,int from)
{
siz[x] = 1;
int ma = 0;
for (int i = 0; i < v[x].size(); i++) {
int to = v[x][i].to;
if (to == from || bo[to]) continue;
Dfs2(to,tot,x);
siz[x] += siz[to];
ma = max(ma,siz[to]);
}
ma = max(ma,tot - siz[x]);
if (ma < Max) O = x,Max = ma;
}
void Build(int x,int from)
{
for (int i = 0; i < v[x].size(); i++) {
int to = v[x][i].to;
if (to == from || bo[to]) continue;
LL w = v[x][i].w;
L[to] = L[x] + 1;
A[to] = (A[x] + w*mi[L[to]-1])%M;
B[to] = (B[x]*ten + w)%M;
if (!A[to]) Ans += 1LL;
if (!B[to]) Ans += 1LL;
Build(to,x);
}
}
int Query(int pos,int goal)
{
for (;;) {
if (vis[pos] != cnt) return 0;
if (vis[pos] == cnt && Num[pos] == goal) return ti[pos];
pos = pos + 1 < h?pos + 1:0;
}
}
void insert(int pos,int goal)
{
for (;;) {
if (vis[pos] == cnt && Num[pos] != goal) {
++pos;
if (pos == h) pos = 0;
continue;
}
if (vis[pos] != cnt) {
vis[pos] = cnt;
ti[pos] = 0;
Num[pos] = goal;
}
++ti[pos];
break;
}
}
void Solve(int x,int from)
{
LL Now = B[x]*inv[L[x]]%M;
Ans += 1LL*Query(Now%h,Now);
for (int i = 0; i < v[x].size(); i++) {
int to = v[x][i].to;
if (to == from || bo[to]) continue;
Solve(to,x);
}
}
void Insert(int x,int from)
{
LL Now = (M - A[x]) % M;
insert(Now%h,Now);
for (int i = 0; i < v[x].size(); i++) {
int to = v[x][i].to;
if (to == from || bo[to]) continue;
Insert(to,x);
}
}
void Dfs1(int x,int tot)
{
Max = INF;
Dfs2(x,tot,0);
int o = O; bo[o] = 1;
A[o] = B[o] = 0;
L[o] = 0; ++cnt;
Build(o,0);
for (int i = 0; i < v[o].size(); i++) {
int to = v[o][i].to;
if (bo[to]) continue;
Solve(to,o);
Insert(to,o);
}
++cnt;
for (int i = v[o].size() - 1; i >= 0; i--) {
int to = v[o][i].to;
if (bo[to]) continue;
Solve(to,o);
Insert(to,o);
}
for (int i = 0; i < v[o].size(); i++) {
int to = v[o][i].to;
if (bo[to]) continue;
Dfs1(to,siz[to]);
}
}
void gcd(LL a,LL &x,LL b,LL &y,LL &g)
{
if (!b) {
x = 1;
y = 0;
g = a;
return;
}
gcd(b,x,a%b,y,g);
LL tmp = y;
y = x - a/b*y;
x = tmp;
}
LL GCD(LL a,LL b,LL k)
{
LL x,y,g;
gcd(a,x,b,y,g);
return (x % M + M) % M;
}
int main()
{
#ifdef DMC
freopen("DMC.txt","r",stdin);
#endif
cin >> n >> M;
LL T = 1; mi[0] = 1;
for (int i = 1; i <= n; i++)
T = T*ten%M;
inv[n] = GCD(T,M,1);
for (int i = 1; i <= n; i++)
mi[i] = mi[i-1]*ten%M;
for (int i = n - 1; i >= 0; i--)
inv[i] = inv[i+1]*ten%M;
for (int i = 1; i < n; i++) {
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
++x; ++y;
v[x].push_back(E(y,w));
v[y].push_back(E(x,w));
}
Dfs1(n/2,n);
cout << Ans;
return 0;
}