1. 程式人生 > 實用技巧 >AGC031F Walk on Graph

AGC031F Walk on Graph

AGC031F

給你一個有$n$個點$m$條邊的無向圖。

$Q$次詢問,每次詢問$S$到$T$,是否有路徑的權值恰好為$R$。

路徑權值的定義:\(\sum_{i=1}^k2^{i-1}c_i \mod MOD\),$c_i$表示經過的第$i$條邊的邊權。

$MOD$給定。\(MOD\le 10^6\)

\(n,m,Q\le 5*10^4\)


神仙題。

首先感覺正著做要記第幾個,不太優美,考慮反著做。記狀態為$(a,x)$表示到達了點$a$,權值為$x$。一次經過邊$(a,b,c)\(的轉移可以到達\)(b,2x+c)$。

接下來分析一波性質:

性質一:

假如在邊$(a,b,c)\(來回走動:\)

(a,x)\to (b,2x+c) \to (a,4x+3c) \to \dots$。走$i$次權值為$2ix+(2i-1)c$。若干次(具體來說是$2$在模$MOD$意義下的秩次)後,權值就會回到$x$。這時候點可能在$a$或$b$,如果這個時候位置在$b$,再走這麼多次位置就會回到$a$。

於是可以發現$(b,2x+c)\(是可以到達\)(a,x)\(的。**那麼\)(a,x)\(和\)(b,2x+c)$之間連的是雙向邊**。

如果我們將所有狀態建出一個圖,那麼問題就是判斷$(T,0)\(和\)(S,R)$是否在同一個連通塊。

性質二:

假如現在的權值是$x$,和當前所在的點(記為$u$)相連的邊有兩條長度為$a$和$b$的邊。

分別來回走一趟,分別可以得到$4x+3a$和$4x+3b$。

換元,將$4x+3a$變成$x$,那麼$x$和$x+3(b-a)$是聯通的

將這個性質擴充套件一下:**一個在遠處的點$v$,和它相連的有兩條長度為$a$和$b$的邊。現在在點$u$,權值$x$和$x+3(b-a)$是聯通的。**考慮如此構造:任選一條從$u$到$v$路徑走過去,權值形如$2kx+\sum_k2c_i$,然後根據前面的結論變成$2kx+\sum_k2c_i+2^k*3(b-a)\(。按照先前的路徑回退回去(如性質一,即\)(b,2x+c)\(到\)(a,x)\()。這樣最終到達狀態\)(u,x+3(b-a))$。

再擴充套件:**選取的這兩條邊可以是任意的兩條邊。**假如$u$和$v$之間有一條權值為$b$的邊,考慮選取和$u$相連的兩條邊$a,b$與和$v$相連的兩條邊$b,c$,那麼可以從$x$得到$x+3(b-a)+3(c-b)=x+3(c-a)$。如果不直接相連,可以類似地擴充套件。

性質三:

設$g=\gcd_{a,b\in E}(a-b)$。其中$a-b$是模$MOD$意義下的。

可以證明$g|MOD$:由於$g|((a-b)\mod MOD),g|((b-a) \mod MOD)$,所以$g|MOD$。

那麼把$\gcd(3g,MOD)$作為新的模數,是可以替代原問題的。因為由數論知識得每個狀態的權值可以任意加減$3g$。

顯然$gcd(3g,MOD)=g或3g$。

接下來是做法:

顯然所有邊的邊權模$g$的值是相同的,記為$z$。

將所有邊權減$z$,狀態中的權值加$z$。

可以發現這樣轉換之後問題是不變的:\((a,x)\to (a,x+z)' \to (b,2(x+z)+c-z)'=(b,2x+c+z)'\to (b,2x+c)\)

(後面不把$'$寫出來了,這裡只是為了方便區分)

考慮從$(u,x)\(出發,到達的狀態一定可以表示成這樣的形式:\)(v,2^px+qg)$

顯然$q\in {0,1,2}$。

由於有$(a,x)\to (b,2x+c) \to (a,4x+3c)$,$c$為$g$的倍數,在對$\gcd(3g,MOD)\(取模之後,就變成了\)(a,4x)$。

因此$p$只需要記錄它的奇偶性。因此取值範圍為${0,1}$。

總共有$6n$種狀態,連邊,並查集維護。

在查詢的時候,相當於從$(T,z)\(出發到\)(S,R+z)$

詢問是否存在$p,q$使得$R+z\equiv 2^{2k+p}z+qg \pmod {\gcd(3g,MOD)},k\in Z$

預處理能表示為$2^{2k+p}z$的點有哪些,然後就可以快速判斷了。


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 50010
int gcd(int a,int b){
	while (b){
		int k=a%b;
		a=b,b=k;
	}
	return a;
}
int n,m,p;
struct EDGE{
	int to,c;
	EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
void link(int u,int v,int c){
	e[ne]=(EDGE){v,c,last[u]};
	last[u]=e+ne++;
}
int g,d,z;
int pz[2][1000010];
struct Dsu{
	Dsu *p;
	Dsu *getfa(){return p==this?p:p=p->getfa();}
} *f[N][2][3];
bool check(int S,int T,int R){
	for (int j=0;j<2;++j)
		for (int k=0;k<3;++k){
			int t=((z+R-k*g)%d+d)%d;
			if (pz[j][t] && f[T][0][0]->getfa()==f[S][j][k]->getfa())
				return 1;			
		}
	return 0;
}
int main(){
	int Q;
	scanf("%d%d%d%d",&n,&m,&Q,&p);
	for (int i=1;i<=m;++i){
		int u,v,c;
		scanf("%d%d%d",&u,&v,&c);
		link(u,v,c),link(v,u,c);
	}
	g=p;
	for (int i=1;i<=n;++i){
		int c1=last[i]->c;
		for (EDGE *ei=last[i]->las;ei;ei=ei->las)
			g=gcd(g,gcd((c1-ei->c+p)%p,(ei->c-c1+p)%p));
	}
	d=gcd(3*g,p);
	z=last[1]->c%g;
	for (int i=1;i<=n;++i)
		for (EDGE *ei=last[i]->las;ei;ei=ei->las)
			ei->c-=z;
	for (int i=1;i<=n;++i)
		for (int j=0;j<2;++j)
			for (int k=0;k<3;++k){
				f[i][j][k]=new Dsu;
				f[i][j][k]->p=f[i][j][k];
			}
	pz[0][z]=1;
	pz[1][z*2%d]=1;
	if (z){
		for (int x=z*4%d;x!=z;x=x*4%d)
			pz[0][x]=1;
		for (int x=z*8%d;x!=z*2%d;x=x*4%d)
			pz[1][x]=1;
	}
	for (int i=1;i<=n;++i)
		for (int j=0;j<2;++j)
			for (int k=0;k<3;++k)
				for (EDGE *ei=last[i];ei;ei=ei->las){
					int j_=(j+1)%2,k_=(k*2+ei->c/g)%(d/g);
					f[ei->to][j_][k_]->getfa()->p=f[i][j][k]->getfa();
				}
	while (Q--){
		int S,T,R;
		scanf("%d%d%d",&S,&T,&R);
		if (check(S,T,R))
			printf("YES\n");
		else
			printf("NO\n");
	}
	return 0;
}