题目链接
https://atcoder.jp/contests/agc031/tasks/agc031_f
题解
这题真是太神仙了……
首先我们转化一下问题,倒着来做,一开始有一个数\(0\), 每次走过一条边该数变为乘以\(2\)再加上这条边的边权。
我们用\((u,x)\)代表一个状态,表示当前在点\(u\),该数值为\(x\), \(x\)始终在\(\mod p\)意义下定义,\(p\)为模数。
假设\(u\)点有一条边连向\(v\)边权为\(w\), 则\((u,x)\)可以变成\((v,2x-w)\)(下用\(\Rightarrow\)表示),然后\((v,2x-w)\Rightarrow(u,4x-3w),(u,4x-3w)\Rightarrow(v,8x-7w),...\)
但是由于\(p\)是奇数,存在\(k>1\)使\(2^k\equiv 1(\mod p)\), 这个序列是循环的,由\((u,x)\)最终依然会到达\((u,x)\). 也就是说,我们可以认为这个递推关系是双向的,\((u,x)\Leftrightarrow (v,2x-w)\Leftrightarrow (u,4x-3w)\Leftrightarrow ...\)
这时,不要往\(2\)的幂的方向思考而一去不复返。考虑这个递推序列的前三步: \((u,x)\Leftrightarrow (u,4x-3w)\)
如果有两条连接的边分别是\(w_1,w_2\), 则\((u,x)\Leftrightarrow (u,4x-3w_1) \Leftrightarrow (u,4x-3w_2)\). 又因为\(4\)在\(\mod p\)意义下有逆元,故\(4x\)可以代表任何数,即\((u,x)\Leftrightarrow (u,x\pm 3(w_1-w_2))\).
考虑一个点相连的那些边,由数论的基本知识可得,\((u,x)\Leftrightarrow (u,x+kt_u)\) (\(k\)为整数),其中\(t_u=\gcd(3g_u,p)\), \(g_u\)为与\(u\)相连所有边权的\(\gcd\). 即所有模\(t_u=\gcd(3g_u,p)\)同余的\(x\), \((u,x)\)的状态是一样的。
再进一步说,考虑不同的点,\((u,x)\Leftrightarrow (v,2x+w)\), \(u\)循环节为\(t_u\)导致\(v\)具有长度为\(2t_u\)的循环节,和其本身的取\(\gcd\)得\(\gcd(t_u,2t_v)=\gcd(t_u,t_v)\) (\(2\)可以去掉是因为\(2\)与\(3g_u\)互质因而与\(t_u\)互质)。由于整个图是联通的,这可以扩展到所有点,也就是对于所有\(u,x\)有\((u,x)\Leftrightarrow (u,x+T)\), 其中\(T=\gcd^n_{u=1}t_u=\gcd(3\gcd^m_{i=1}w_i,p)\). 我们令\(G=\gcd^m_{i=1}w_i\). 显然\(T=G\)或\(T=3G\).
整理一下,同一点所有模\(T\)同余的状态是一样的,而所有边权是模\(G\)同余的,且\(T=G\)或\(T=3G\).
这样,我们可以把题目中的\(p\)直接改成\(T\), 不会对答案有任何影响。下面我们用\(p\)来代表\(T\).
注: 这里之所以我们只取了\((4x-3y)\)这一项而没有继续深入下去依然能得到正确的结论的原因是对任何\(k\in \mathbb{N}\), \((u,2^{2k}x+(2^{2k}-1)w)\), 而\(3|(2^{2k}-1)\).
既然所有边权是模\(G\)同余的,我们就可以设\(w_i=c_iG+z\), 其中\(0\le z\lt G\)是一个对于所有的边\(i\)都一样的数。
这个边权带常数\(z\)看上去很不优美,我们想办法把它消掉!我们把所有的当前数值\(x\)都增加\(z\), 并把所有的边权都减少\(z\). 改变后的值记为\(x'\), 那么现在的一次游走将会是: \(x'-z=x\rightarrow (2x+c_iG+z)=(2(x+z)+c_iG)-z=(2x'+c_iG)-z\), 也就是说\(x'\)经过一次操作会变成\(2x'+c_iG\), \(z\)的余数没了,其余操作没有变化!
于是我们执行把数值\(x\)增加\(z\)同时把边权减少\(z\)的操作,现在我们认为\(w_i=c_iG\),且询问的是\((t,z)\)与\((s,z+r)\)是否可达。
因为\(w_i=c_iG\),故\((u,x)\Leftrightarrow (u,4x+3c_iG)\Leftrightarrow (u,4x)\), 且因为边权全部是\(G\)的倍数,在\(\mod 3G\)意义下只有\(3\)种取值,即可以认为\(c_i=0,1,2\). 那每个状态就可以表示成\((u,2^jx+kG)\)的形式,其中\(0\le j\lt 2,0\le k\lt 3\).
也就是说,对于每个点,我们只有\(6\)种状态有用!
然后的做法就很显然了,我们建出这\(6n\)个状态,对于每一条输入的边,操作权值之后把相应的状态用并查集缩起来。
对于询问,我们想要的是\((t,z)\)和\((s,z+r)\)是否联通。枚举\(j\mod 2\)与\(k\), 我们得到\(2^jz+kG\equiv z+r(\mod p)\). 我们需要判断是否存在\(j\)满足\(2^jz\equiv z+r-kG(\mod p)\), 这个对于每个\(j\)预处理一下\(2^jz\)记录下来奇偶即可。若存在且\((t,0,0)\)和\((s,j,k)\)这两个状态联通,则答案是YES
, 否则是NO
.
时间复杂度\(O(6(n+m+q)\alpha(n)+p)\).
代码
#include
#define llong long long
#define mkpr make_pair
#define riterator reverse_iterator
using namespace std;
inline int read()
{
int x = 0,f = 1; char ch = getchar();
for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}
for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}
return x*f;
}
const int N = 5e4;
const int mxP = 1e6;
struct AEdge
{
int u,v,w;
} ae[N+3];
int uf[N*6+3];
bool ispw2[mxP+3][2];
int n,m,q,p,g;
int gcd(int x,int y) {return y==0?x:gcd(y,x%y);}
int getid(int x,int j,int k) {return (j*3+k)*n+x;}
int findfa(int u) {return u==uf[u]?u:uf[u]=findfa(uf[u]);}
void unionfa(int u,int v)
{
int x = findfa(u),y = findfa(v);
if(x!=y) {uf[x] = y;}
}
int main()
{
scanf("%d%d%d%d",&n,&m,&q,&p); g = p;
for(int i=1; i<=n*6; i++) uf[i] = i;
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&ae[i].u,&ae[i].v,&ae[i].w);
g = gcd(g,abs(ae[i].w-ae[1].w));
}
p = gcd(p,3*g);
int z = ae[1].w%g;
for(int i=1; i<=m; i++)
{
int w = (ae[i].w-z)/g%3,u = ae[i].u,v = ae[i].v;
for(int j=0; j<2; j++) for(int k=0; k<3; k++)
{
unionfa(getid(u,j,k),getid(v,j^1,(k+k+w)%3));
unionfa(getid(v,j,k),getid(u,j^1,(k+k+w)%3));
}
}
for(int i=0,x=z; i