【数论+并查集】Walk on Graph

题目

有一张n个点m条边的无向连通图G,每条边有长度ci,有一个人在上面走
有q组询问,每组询问给出si,ti,ri,表示问你是否存在一条从si出发到ti结束长度为ri%Mod的路径
注意这里的路径长度是∑ci*2^i
n,m,q<=50000,Mod<=1000000且Mod为奇数

思路

考虑把这个过程倒过来,这样每走一次就会变成 2 x + w 2x + w 2x+w

朴素做法是判断到某个点,值为 x x x 是否可行,考虑寻找一些性质来优化这个做法。

不难发现直接做的话是单向边,这样处理起来比较困难。

考虑一条边 ( u , v , w ) (u, v, w) (u,v,w),如果在这条边上进行左右横跳的话,可以从 ( u , x ) (u, x) (u,x) 转移到 ( v , 2 x + w ) (v, 2x + w) (v,2x+w),即一个状态有唯一后继。同时因为模数为奇数,这一过程是可逆的,所以一个状态有唯一前驱。因此这会形成一个环。我们不断在这个环上走,可以从 ( v , 2 x + w ) (v, 2x + w) (v,2x+w) 走到 ( u , x ) (u, x) (u,x)。因此可达性是双向的。

考虑一个点的某两条出边 ( a , b , w 1 ) , ( a , c , w 2 ) (a, b, w_1), (a, c, w_2) (a,b,w1),(a,c,w2),那么可以得到状态 ( a , 4 x + 3 w 1 ) , ( a , 4 x + 3 w 2 ) (a, 4x + 3w_1), (a, 4x + 3w_2) (a,4x+3w1),(a,4x+3w2),因此 ( a , x ) (a, x) (a,x) ( a , x + 3 ( w 1 − w 2 ) ) (a, x + 3(w_1 - w_2)) (a,x+3(w1w2)) 是互相可达的。显然这个连通块中任意一个点 p p p 都满足 ( p , x ) (p, x) (p,x) ( p , x + 3 ( w 1 − w 2 ) ) (p, x + 3(w_1 - w_2)) (p,x+3(w1w2)) 是互相可达的。

考虑如果一对边边权的差为 d d d,那么我可以让 ( p , x ) (p,x) (p,x) 到达 ( p , x + 3 d ) (p, x +3d) (p,x+3d) 。证明考虑从一条边到另外一条边的路径,不难用中间的点来得到这个差。

因此设所有边两两之差的最大公约数为 d d d,设 g = ( M O D , 3 d ) g = (MOD, 3d) g=(MOD,3d),那么 ( p , x ) (p, x) (p,x) ( p , x + g ) (p, x + g) (p,x+g) 都是可以互相到达的。

注意到此时任意一条边的边权为 k d + r kd + r kd+r。先考虑一下 r = 0 r = 0 r=0 怎么做。此时任意一个点的状态都可以表示为 t d td td,我们可以把值对 g g g 取模,因此我们只关心 t t t 3 3 3 取模后的余数。然后就是一个点数只有 3 n 3n 3n 的图判断连通性,直接并查集维护就行了。

现在考虑 r ≠ 0 r \neq 0 r=0 的情形。注意到所有边的 r r r 都是相同的,最终路径的权值是 ∑ i 2 i ( k i d + r ) = k d + ( 2 l − 1 ) r \sum_{i} 2^i (k_i d + r) = kd + (2^l- 1) r i2i(kid+r)=kd+(2l1)r 。现在比较难处理的问题是 2 l − 1 2^l - 1 2l1。考虑左后横跳可以在不改变 k m o d    3 k\mod 3 kmod3 的情况下使得 l l l 增加 2。因此枚举 k k k l l l 的奇偶性,判断是否存在一个 l l l 使得 k d + ( 2 l − 1 ) r = R kd + (2^l - 1) r = R kd+(2l1)r=R。现在状态数只有 6 n 6n 6n 并查集维护即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+77;
int n,m,q,mod,s,t,r,u[N],v[N],c[N],g,fa[N];
bool can[2][N];
int Id(int x,int y,int z)
{
	return (x-1)*6+y*3+z;
}
int get(int x)
{
	return fa[x]==x?x:fa[x]=get(fa[x]);
}
int gcd(int x,int y)
{
	if(y==0) return x;
	return gcd(y,x%y);
}
void link(int x,int y)
{
	x=get(x);y=get(y);
	if(x==y) return;
	fa[x]=y;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&q,&mod);
	for(int i=1; i<=m; i++)
	{
		scanf("%d%d%d",&u[i],&v[i],&c[i]);
		g=gcd(g,abs(c[i]-c[1]));
	}
	if(!g) g=mod;mod=gcd(mod,3*g);int z=c[1]%g;
	for(int i=0; i<=6*n; i++) fa[i]=i;
	for(int i=1; i<=m; i++)
	{
		int w=(c[i]-z)/g;
		for(int p=0; p<=1; p++) for(int q=0; q<=2; q++)
		{
			link(Id(u[i],p,q),Id(v[i],1-p,(2*q+w)%3));
			link(Id(v[i],p,q),Id(u[i],1-p,(2*q+w)%3));
		}
	}
	for(int i=0,j=z;i<mod<<1;i++,(j<<=1)%=mod) can[i&1][j]=1;
	for(;q;q--)
	{
		scanf("%d%d%d",&s,&t,&r);
		int ret=0;
		for(int p=0; p<=1; p++) for(int q=0; q<=2; q++) if(get(Id(t,0,0))==get(Id(s,p,q))) ret|=can[p][(r+z+(3-q)*g)%mod];
		puts(ret?"YES":"NO");
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值