Address
Algorithm 1
设 f x , i f_{x,i} fx,i 表示是否存在终点在点 x x x 且权值和模 m m m 的值为 i i i 的路径,对每组询问记忆化搜索即可,时间复杂度 O ( n m q ) \mathcal O(nmq) O(nmq),期望得分 11pts \text{11pts} 11pts。
Algorithm 2
针对 m = 2 m = 2 m=2 的数据。
询问时 u u u 到 v v v 之间的任意一条路径可以看做是 u u u 到 v v v 之间的某一条路径再加上连通块中若干个环的边权和的和,所以可以用并查集维护出每个点到并查集根的任意一条路径的边权和,一旦所在连通块中存在边权和为 1 1 1 的环,就可以构造出任意权值的路径,否则 u , v u,v u,v 之间的路径和可以看做是到并查集根的路径和。
时间复杂度 O ( q α ( n ) + q c + n ) \mathcal O(q\alpha(n) + qc + n) O(qα(n)+qc+n),结合算法一期望得分 32pts \text{32pts} 32pts。
Algorithm 3
针对 m m m 是质数的数据。
由同余的理论可知:
{
x
∣
x
=
k
w
m
o
d
m
,
k
∈
N
}
=
{
x
∣
x
=
k
gcd
(
w
,
m
)
m
o
d
m
,
k
∈
N
}
\{x|x = kw \bmod m, k \in \mathbb N\} = \{x | x = k\gcd(w,m) \bmod m, k \in \mathbb N\}
{x∣x=kwmodm,k∈N}={x∣x=kgcd(w,m)modm,k∈N}
m
=
2
m = 2
m=2 的情况用算法二处理。
对于 m > 2 m > 2 m>2 的情况下, 询问时若 u , v u,v u,v 所在连通块中存在边权 w w w 模 m m m 的值不为 0 0 0 的边,因为 gcd ( 2 w , m ) = 1 \gcd(2w,m) = 1 gcd(2w,m)=1,由上述理论可知,我们只要在走到这条边上之后在这条边上不停地绕圈,就可以构造出任意权值的路径,因此在用并查集维护时额外记录连通块是否存在边权模 m m m 的值不为 0 0 0 的边即可。
时间复杂度 O ( q α ( n ) + q c + n ) \mathcal O(q\alpha(n) + qc + n) O(qα(n)+qc+n),结合前述算法期望得分 45pts \text{45pts} 45pts。
Algorithm 4
针对 m m m 是奇数的数据。
同样地,询问时对于
u
,
v
u,v
u,v 所在连通块中边权为
w
w
w 的边,因为
gcd
(
2
w
,
m
)
=
gcd
(
w
,
m
)
\gcd(2w,m) = \gcd(w,m)
gcd(2w,m)=gcd(w,m),设连通块中的边集为
E
E
E,则
u
,
v
u,v
u,v 间所有路径的边权和组成的集合为:
{
x
∣
x
=
k
gcd
(
m
,
gcd
j
∈
E
w
j
)
m
o
d
m
,
k
∈
N
}
\{x | x = k \gcd(m, \gcd_{j \in E} w_j) \bmod m, k \in \mathbb N\}
{x∣x=kgcd(m,j∈Egcdwj)modm,k∈N}
令
g
=
gcd
(
m
,
gcd
j
∈
E
w
j
)
g = \gcd(m, \gcd_{j \in E}w_j)
g=gcd(m,gcdj∈Ewj),因为
f
i
f_i
fi 是一个等差数列,对于合法的
i
i
i 可以列出一个同余方程:
x
+
b
i
≡
0
(
m
o
d
g
)
b
i
≡
−
x
(
m
o
d
g
)
\begin{aligned} x + bi &\equiv 0 \pmod g \\ bi &\equiv - x \pmod g \\ \end{aligned}
x+bibi≡0(modg)≡−x(modg)
用扩展欧几里得算法解之即可,若有解,求出最小正整数解
t
t
t,则解集为
{
i
∣
i
=
t
+
k
g
(
g
,
b
)
,
k
∈
Z
}
\{i | i = t + k\frac{g}{(g,b)}, k \in \mathbb Z\}
{i∣i=t+k(g,b)g,k∈Z},不难算出
[
0
,
c
)
[0, c)
[0,c) 内的解数。
同样用并查集维护连通块内所有边权和 m m m 的 gcd \gcd gcd 即可。
时间复杂度 O ( q α ( n ) + q log m + n ) \mathcal O(q \alpha(n) + q\log m + n) O(qα(n)+qlogm+n),结合前述算法期望得分 62pts \text{62pts} 62pts。
Algorithm 5
考虑 m m m 没有任何限制的情况。
类似算法二,我们可以把 u , v u,v u,v 之间的任意一条路径表示成 u , v u,v u,v 之间的某一条路径加上 m m m 和连通块中所有环边权和的 gcd \gcd gcd 的若干倍。
在路径中加入一个环可以通过以下方式构造:在从点 u u u 到这个环的路径上来回走 m m m 遍,在中间的某一次加入这个环。
我们只需要在加入一条边时,加入绕这条边一圈的边权和以及这条边和原来的那些边新形成的某一个环的边权和,就可以维护出所有环边权和的 gcd \gcd gcd。
考虑这样做的正确性,我们只需要讨论将两个环合并的合法性,合并时只有以下两种情况:
-
若两个环只有一个交点,设两环的边权和为 a , b a,b a,b,显然有 gcd ( a , b ) ∣ ( a + b ) \gcd(a,b) | (a + b) gcd(a,b)∣(a+b);
-
若两个环的交是一条链,且链的边权和为 c c c,设两环的边权和为 a + c , b + c ( a ≥ b ) a + c, b + c(a \ge b) a+c,b+c(a≥b),我们只需要证明:
gcd ( 2 a , gcd ( a + c , b + c ) ) ∣ ( a + b ) \gcd(2a, \gcd(a + c, b + c)) | (a + b) gcd(2a,gcd(a+c,b+c))∣(a+b)
由辗转相减法,可得:
gcd ( 2 a , gcd ( a + c , b + c ) ) = gcd ( 2 a , gcd ( a − b , b + c ) ) = gcd ( gcd ( 2 a , a − b ) , b + c ) = gcd ( gcd ( a + b , a − b ) , b + c ) \begin{aligned} \gcd(2a, \gcd(a + c, b + c)) &= \gcd(2a, \gcd(a - b, b + c))\\ &= \gcd(\gcd(2a, a - b), b + c)\\ &= \gcd(\gcd(a + b, a - b), b + c)\\ \end{aligned} gcd(2a,gcd(a+c,b+c))=gcd(2a,gcd(a−b,b+c))=gcd(gcd(2a,a−b),b+c)=gcd(gcd(a+b,a−b),b+c)
显然最后划得的结果整除 ( a + b ) (a + b) (a+b),原命题得证。
同算法四对 i i i 列出同余方程解之即可。
时间复杂度 O ( q α ( n ) + q log m + n ) \mathcal O(q\alpha(n) + q\log m + n) O(qα(n)+qlogm+n),期望得分 100pts \text{100pts} 100pts。
Code
#include <bits/stdc++.h>
const int S = 1 << 20;
char frd[S], *ihead = frd + S;
const char *itail = ihead;
inline char nxtChar()
{
if (ihead == itail)
fread(frd, 1, S, stdin), ihead = frd;
return *ihead++;
}
template <class T>
inline void read(T &res)
{
char ch;
while (ch = nxtChar(), !isdigit(ch));
res = ch ^ 48;
while (ch = nxtChar(), isdigit(ch))
res = res * 10 + ch - 48;
}
char fwt[S], *ohead = fwt;
const char *otail = ohead + S;
inline void outChar(char ch)
{
if (ohead == otail)
fwrite(fwt, 1, S, stdout), ohead = fwt;
*ohead++ = ch;
}
template <class T>
inline void put(T x)
{
if (x > 9)
put(x / 10);
outChar(x % 10 + 48);
}
typedef long long ll;
const int N = 1e6 + 5;
int sze[N], fa[N], d[N], g[N];
int n, m, q;
inline ll exgcd(ll a, ll b, ll &x, ll &y)
{
if (!b)
return x = 1, y = 0, a;
ll e = exgcd(b, a % b, y, x);
y -= a / b * x;
return e;
}
inline void add(int &x, int y)
{
x += y;
x >= m ? x -= m : 0;
}
inline void dec(int &x, int y)
{
x -= y;
x < 0 ? x += m : 0;
}
inline int ufs_find(int x)
{
if (fa[x] != x)
{
int t = ufs_find(fa[x]);
add(d[x], d[fa[x]]);
fa[x] = t;
return t;
}
return x;
}
int main()
{
freopen("path.in", "r", stdin);
freopen("path.out", "w", stdout);
read(n); read(m); read(q);
for (int i = 1; i <= n; ++i)
{
sze[i] = 1;
fa[i] = i;
d[i] = 0;
g[i] = m;
}
int opt, u, v, x, b, c;
while (q--)
{
read(opt);
read(u); read(v); read(x);
int tu = ufs_find(u),
tv = ufs_find(v);
if (opt == 1)
{
if (tu == tv)
{
int w1 = d[u], w2 = x;
add(w1, d[v]);
add(w1, x);
add(w2, x);
g[tu] = std::__gcd(g[tu], std::__gcd(w1, w2));
}
else
{
if (sze[tu] > sze[tv])
std::swap(tu, tv), std::swap(u, v);
sze[tv] += sze[tu];
fa[tu] = tv;
d[tu] = d[u];
add(d[tu], d[v]);
add(d[tu], x);
int w = x;
add(w, x);
g[tv] = std::__gcd(w, std::__gcd(g[tv], g[tu]));
}
}
else
{
read(b); read(c);
if (tu != tv)
outChar('0');
else
{
int w = d[u];
add(w, d[v]);
dec(w, x);
w %= g[tu];
ll x, y, e = exgcd(b, g[tu], x, y);
if (w % e != 0)
outChar('0');
else
{
ll tmp = g[tu] / e;
x = x % tmp;
x < 0 ? x += tmp : 0;
x = x * w / e % tmp;
if (x >= c)
outChar('0');
else
put((c - 1 - x) / tmp + 1);
}
}
outChar('\n');
}
}
fwrite(fwt, 1, ohead - fwt, stdout);
fclose(stdin); fclose(stdout);
return 0;
}