AtCoder AGC031F Walk on Graph (图论、数论)

题目链接

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

你可能感兴趣的:(AtCoder AGC031F Walk on Graph (图论、数论))