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^kequiv 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,xpm 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(3gcd^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))这一项而没有继续深入下去依然能得到正确的结论的原因是对任何(kin mathbb{N}), ((u,x)Leftrightarrow (u,2^{2k}x+(2^{2k}-1)w)), 而(3|(2^{2k}-1)).

既然所有边权是模(G)同余的,我们就可以设(w_i=c_iG+z), 其中(0le zlt G)是一个对于所有的边(i)都一样的数。
这个边权带常数(z)看上去很不优美,我们想办法把它消掉!我们把所有的当前数值(x)都增加(z), 并把所有的边权都减少(z). 改变后的值记为(x'), 那么现在的一次游走将会是: (x'-z=x ightarrow (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))的形式,其中(0le jlt 2,0le klt 3).
也就是说,对于每个点,我们只有(6)种状态有用!
然后的做法就很显然了,我们建出这(6n)个状态,对于每一条输入的边,操作权值之后把相应的状态用并查集缩起来。
对于询问,我们想要的是((t,z))((s,z+r))是否联通。枚举(jmod 2)(k), 我们得到(2^jz+kGequiv z+r(mod p)). 我们需要判断是否存在(j)满足(2^jzequiv 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<bits/stdc++.h>
#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<p; i++,x=(x<<1)%p) ispw2[x][i&1] = true;
	while(q--)
	{
		int u,v,l; scanf("%d%d%d",&u,&v,&l); bool ans = false;
		for(int j=0; j<2; j++) for(int k=0; k<3; k++)
		{
			int tmp = (z+l+(3-k)*g)%p;
			if(ispw2[tmp][j])
			{
				int uu = findfa(getid(v,0,0)),vv = findfa(getid(u,j,k));
				if(uu==vv) {ans = true; break;}
			}
		}
		if(ans==true) puts("YES"); else puts("NO");
	}
	return 0;
}
原文地址:https://www.cnblogs.com/suncongbo/p/12285863.html