我是靠谱客的博主 长情台灯,最近开发中收集的这篇文章主要介绍AGC031F Walk on Graph,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

AGC031F

给你一个有 n n n个点 m m m条边的无向图。

Q Q Q次询问,每次询问 S S S T T T,是否有路径的权值恰好为 R R R

路径权值的定义: ∑ i = 1 k 2 i − 1 c i m o d    M O D sum_{i=1}^k2^{i-1}c_i mod MOD i=1k2i1cimodMOD c i c_i ci表示经过的第 i i i条边的边权。

M O D MOD MOD给定。 M O D ≤ 1 0 6 MODle 10^6 MOD106

n , m , Q ≤ 5 ∗ 1 0 4 n,m,Qle 5*10^4 n,m,Q5104


神仙题。

首先感觉正着做要记第几个,不太优美,考虑反着做。记状态为 ( a , x ) (a,x) (a,x)表示到达了点 a a a,权值为 x x x。一次经过边 ( a , b , c ) (a,b,c) (a,b,c)的转移可以到达 ( b , 2 x + c ) (b,2x+c) (b,2x+c)

接下来分析一波性质:

性质一:

假如在边 ( a , b , c ) (a,b,c) (a,b,c)来回走动: ( a , x ) → ( b , 2 x + c ) → ( a , 4 x + 3 c ) → … (a,x)to (b,2x+c) to (a,4x+3c) to dots (a,x)(b,2x+c)(a,4x+3c)。走 i i i次权值为 2 i x + ( 2 i − 1 ) c 2^ix+(2^i-1)c 2ix+(2i1)c。若干次(具体来说是 2 2 2在模 M O D MOD MOD意义下的秩次)后,权值就会回到 x x x。这时候点可能在 a a a b b b,如果这个时候位置在 b b b,再走这么多次位置就会回到 a a a

于是可以发现 ( b , 2 x + c ) (b,2x+c) (b,2x+c)是可以到达 ( a , x ) (a,x) (a,x)的。那么 ( a , x ) (a,x) (a,x) ( b , 2 x + c ) (b,2x+c) (b,2x+c)之间连的是双向边

如果我们将所有状态建出一个图,那么问题就是判断 ( T , 0 ) (T,0) (T,0) ( S , R ) (S,R) (S,R)是否在同一个连通块。

性质二:

假如现在的权值是 x x x,和当前所在的点(记为 u u u)相连的边有两条长度为 a a a b b b的边。

分别来回走一趟,分别可以得到 4 x + 3 a 4x+3a 4x+3a 4 x + 3 b 4x+3b 4x+3b

换元,将 4 x + 3 a 4x+3a 4x+3a变成 x x x那么 x x x x + 3 ( b − a ) x+3(b-a) x+3(ba)是联通的

将这个性质扩展一下:一个在远处的点 v v v,和它相连的有两条长度为 a a a b b b的边。现在在点 u u u,权值 x x x x + 3 ( b − a ) x+3(b-a) x+3(ba)是联通的。 考虑如此构造:任选一条从 u u u v v v路径走过去,权值形如 2 k x + ∑ i = 1 k 2 i − 1 c i 2^kx+sum_{i=1}^k2^{i-1}c_i 2kx+i=1k2i1ci,然后根据前面的结论变成 2 k x + ∑ i = 1 k 2 i − 1 c i + 2 k ∗ 3 ( b − a ) 2^kx+sum_{i=1}^k2^{i-1}c_i+2^k*3(b-a) 2kx+i=1k2i1ci+2k3(ba)。按照先前的路径回退回去(如性质一,即 ( b , 2 x + c ) (b,2x+c) (b,2x+c) ( a , x ) (a,x) (a,x))。这样最终到达状态 ( u , x + 3 ( b − a ) ) (u,x+3(b-a)) (u,x+3(ba))

再扩展:选取的这两条边可以是任意的两条边。 假如 u u u v v v之间有一条权值为 b b b的边,考虑选取和 u u u相连的两条边 a , b a,b a,b与和 v v v相连的两条边 b , c b,c b,c,那么可以从 x x x得到 x + 3 ( b − a ) + 3 ( c − b ) = x + 3 ( c − a ) x+3(b-a)+3(c-b)=x+3(c-a) x+3(ba)+3(cb)=x+3(ca)。如果不直接相连,可以类似地扩展。

性质三:

g = gcd ⁡ a , b ∈ E ( a − b ) g=gcd_{a,bin E}(a-b) g=gcda,bE(ab)。其中 a − b a-b ab是模 M O D MOD MOD意义下的。

可以证明 g ∣ M O D g|MOD gMOD:由于 g ∣ ( ( a − b ) m o d    M O D ) , g ∣ ( ( b − a ) m o d    M O D ) g|((a-b)mod MOD),g|((b-a) mod MOD) g((ab)modMOD),g((ba)modMOD),所以 g ∣ M O D g|MOD gMOD

那么把 gcd ⁡ ( 3 g , M O D ) gcd(3g,MOD) gcd(3g,MOD)作为新的模数,是可以替代原问题的。因为由数论知识得每个状态的权值可以任意加减 3 g 3g 3g

显然 g c d ( 3 g , M O D ) = g 或 3 g gcd(3g,MOD)=g或3g gcd(3g,MOD)=g3g

接下来是做法:

显然所有边的边权模 g g g的值是相同的,记为 z z z

将所有边权减 z z z,状态中的权值加 z z z

可以发现这样转换之后问题是不变的: ( a , x ) → ( a , x + z ) ′ → ( b , 2 ( x + z ) + c − z ) ′ = ( b , 2 x + c + z ) ′ → ( b , 2 x + c ) (a,x)to (a,x+z)' to (b,2(x+z)+c-z)'=(b,2x+c+z)'to (b,2x+c) (a,x)(a,x+z)(b,2(x+z)+cz)=(b,2x+c+z)(b,2x+c)

(后面不把 ′ ' 写出来了,这里只是为了方便区分)

考虑从 ( u , x ) (u,x) (u,x)出发,到达的状态一定可以表示成这样的形式: ( v , 2 p x + q g ) (v,2^px+qg) (v,2px+qg)

显然 q ∈ { 0 , 1 , 2 } qin {0,1,2} q{0,1,2}

由于有 ( a , x ) → ( b , 2 x + c ) → ( a , 4 x + 3 c ) (a,x)to (b,2x+c) to (a,4x+3c) (a,x)(b,2x+c)(a,4x+3c) c c c g g g的倍数,在对 gcd ⁡ ( 3 g , M O D ) gcd(3g,MOD) gcd(3g,MOD)取模之后,就变成了 ( a , 4 x ) (a,4x) (a,4x)

因此 p p p只需要记录它的奇偶性。因此取值范围为 { 0 , 1 } {0,1} {0,1}

总共有 6 n 6n 6n种状态,连边,并查集维护。

在查询的时候,相当于从 ( T , z ) (T,z) (T,z)出发到 ( S , R + z ) (S,R+z) (S,R+z)

询问是否存在 p , q p,q p,q使得 R + z ≡ 2 2 k + p z + q g ( m o d gcd ⁡ ( 3 g , M O D ) ) , k ∈ Z R+zequiv 2^{2k+p}z+qg pmod {gcd(3g,MOD)},kin Z R+z22k+pz+qg(modgcd(3g,MOD)),kZ

预处理能表示为 2 2 k + p z 2^{2k+p}z 22k+pz的点有哪些,然后就可以快速判断了。


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("YESn");
		else
			printf("NOn");
	}
	return 0;
}

最后

以上就是长情台灯为你收集整理的AGC031F Walk on Graph的全部内容,希望文章能够帮你解决AGC031F Walk on Graph所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(63)

评论列表共有 0 条评论

立即
投稿
返回
顶部