P12604 题解

模拟赛出了这题,场切了但是因为各种边界和特判调了 inf h,决定写篇题解记录一下。

题意很简单,就是给定一张图,询问 \(x,y,z\) 表示 \(x\)\(y\) 路径长度模 \(z\) 的最小值,乍一看是十分不可做的,但是题目没有要求路径为简单路径,所以可以在一条边或一个环上反复横跳。

首先对一条边考虑,设边权为 \(w\),那么如果 \(\gcd(2w,z)=1\),则答案为 \(0\),证明考虑裴蜀定理,\(\gcd(2w,z)=1\) 可推知存在 \(x,y\) 满足 \(2wx+zy=k\),其中 \(k\) 为正整数,那么通过在这一条边上反复跳跃必然能做到无论其他边边权如何,都能解出一组答案为 \(0\) 的情况。

这个结论太弱了,考虑扩展一下,计算两条边的情况,设边权分别为 \(w_1,w_2\),则有 \(\min((2w_1a+2w_2b) \mod z)=\gcd(2w_1,2w_2,z)\),证明还是考虑裴蜀定理。

继续扩展,不难使用归纳法证明只要满足 \(\gcd(2\gcd_{i=1}^{m} w_i,z)=1\),那么答案为 \(0\)

这个性质足够了,考虑什么时候那个条件不满足。

首先,若 \(z\) 为奇数,答案一定为 \(0\),因为一条边走奇数次可以走到令一个端点,于是只要找一条路径,每条边走 \(z\) 次即可。

其次,若 \(z\) 为偶数,我们考虑先将所有 \(w_i\)\(z\) 都除以 \(\gcd(2\gcd_{i=1}^{m} w_i,z)\),注意这里有可能会除出来 \(0.5\) 这种的诡异情况,所以要提前把 \(w_i\)\(2\) 来避免小数,最后询问时要把 \(2\) 去了。

然后,我们的结论是:在边权重新赋值的图中,若 \(x\)\(y\) 存在长度为偶数的路径,则答案为 \(0\),只需要在路径上选几条边来回几次即可;若不存在,则答案一定大于 \(0\),但可以通过上面所说的方法调整到 \(1\)

于是我们只需要求出是否存在长度为偶数的路径就好了,如果不考虑除法,那么可以直接使用扩展域并查集。具体而言,每个点拆成 \(i\)\(i+n\),变成两个集合,遇到偶数边则同集合连边,否则跨集合连边,查询时只要查询 \(x,y\) 是否在一个连通块里就好了。

然后我们注意到 \(\gcd(2\gcd_{i=1}^{m} w_i,z)\) 不为 \(2\) 的质因子对奇偶性没有影响,所以只需要预处理出所有 \(w_i\) 除以 \(2^k\) 的情况就好了,相当于开 \(\log_2 V\) 个并查集维护。

时间复杂度 \(O(n\log n\log V)\),如果用了启发式合并就是 \(O(n\alpha(n)\log V)\) 的。但是一般来说路径压缩不会被卡满,所以没有必要写启发式合并。而且这题写启发式合并会 MLE

其他细节看代码吧。

code

#include <bits/stdc++.h>
// #define BIG
// #define MID
#ifdef BIG
#define int __int128
#endif
#ifdef MID
#define int long long
#endif
using namespace std;
const int N=1e6+5;
int T,n,m,q,fa[31][N<<1],u[N],v[N],w[N],G;
bool vis[N];
inline int read(){
	int ret=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}
inline void write(int x){
	if(x<0)putchar('-'),x=-x;
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
inline void writesp(int x){write(x),putchar(' ');}
inline void writeln(int x){write(x),putchar('\n');}
inline int getfa(int c,int x){return fa[c][x]==x?x:fa[c][x]=getfa(c,fa[c][x]);}
inline void merge(int c,int x,int y){
	int fx=getfa(c,x),fy=getfa(c,y);
	if(fx==fy)return;fa[c][fx]=fy;
}
void Ready(int t,int subtask_id){
	T=t;
}
void Set(int N,int M,int Q,vector<int> U,vector<int> V,vector<int> W){
	n=N,m=M,q=Q;
	for(int i=0;i<m;i++)u[i+1]=U[i],v[i+1]=V[i],w[i+1]=W[i];G=0;
	for(int i=1;i<=m;i++)G=__gcd(G,w[i]),w[i]<<=1;
	iota(fa[0]+1,fa[0]+2*n+1,1);
	for(int i=1;i<=m;i++)
		if(w[i]&1)merge(0,u[i],v[i]+n),merge(0,u[i]+n,v[i]);else merge(0,u[i],v[i]),merge(0,u[i]+n,v[i]+n);
	for(int i=1;i<=m;i++)vis[i]=0;
	for(int j=1;j<=30;j++){
		for(int i=1;i<=m;i++)
			if(!(w[i]&1))w[i]>>=1;else vis[i]=1;
		iota(fa[j]+1,fa[j]+2*n+1,1);
		for(int i=1;i<=m;i++){
			if(vis[i])continue;
			if(w[i]&1)merge(j,u[i],v[i]+n),merge(j,u[i]+n,v[i]);else merge(j,u[i],v[i]),merge(j,u[i]+n,v[i]+n);
		}
	}
}
int Go(int x,int y,int z){
	if(!G)return 0;if(z&1)return 0;
	int g=__gcd(2*G,z),c=__builtin_ctz(g);
	z/=g;if(!(g&1))g>>=1;
	if(getfa(c,x)==getfa(c,y))return 0;
	else return g;
}

闲话:这题真的只有蓝吗,建议升紫。

posted on 2026-08-22 13:47  Vector_net  阅读(3)  评论(0)    收藏  举报