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)\(來回走動:\)
於是可以發現$(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;
}