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;
}
闲话:这题真的只有蓝吗,建议升紫。
浙公网安备 33010602011771号