题解:CF1515G Phoenix and Odometers

· · 题解

要求的是是否存在一个包含点 v 的环,使得环的长度模 t-s 同余。

那么有关的部分就是包含点 v 的强连通分量。由于对于分量上的任意点,存在一个包含其与 v 的环,那么只需要跑 t 遍该环,就可以在某一时刻“出现”在这个点上。又由于复杂环可以由简单环构成,那么问题转化为在强连通分量上选若干个简单环,始环满足长度和模 t-s 同余。可以列出线性同余方程,由裴蜀定理,可知 \gcd(简单环长度集合S \cup t) 整除 t-s

需要求出强连通分量内环长的最大公因数 G_0,猜想是要把所有环都套进一个生成树上统一去维护。要把生成树补成一个强连通图,那么考虑一个加入非树边 (u,v) 的顺序:v 始终能到达根节点。根据强连通性,显然这是可以构造出的。当加入一条边时,就加入 根节点到 u 的路径 + v 所有到根节点的路径 构成的环。由辗转相减术,实际上相当于加入 dep_u +w-dep_v 。这与具体加号右边的东西是无关的。最后我们得到这些数的最大公因数 G,含义是包含根节点的环长度的 \gcd。据定义得到 G_0 整除 G

但是容易发现:图上任意的环长度都可以由加入的元素的和构成。有:

推完以后觉得真是精妙绝伦,天作之合。后来在题解区看见了一句概括:所有的环都至少与另一个环进行了求差后求 $\gcd$,等于原来的 $\gcd$。得到了完全的理解。 ```cpp #include <bits/stdc++.h> using namespace std; int gcd(int x,int y){return y == 0 ? x : gcd(y, x%y);} int n, m, q; vector<int> vec[200002], val[200002]; int dfntot, dfn[200002], low[200002]; int sttop, st[200002]; bool inst[200002]; int belong[200002]; void tarjan(int pos){ dfn[pos] = ++dfntot; low[pos] = dfn[pos]; st[++sttop] = pos, inst[pos] = 1; for(int i=0;i<vec[pos].size();i++){ int v = vec[pos][i]; if(dfn[v] != 0){ if(inst[v]) low[pos] = min(low[pos], dfn[v]); } else { tarjan(v); low[pos] = min(low[pos], low[v]); } } if(low[pos] == dfn[pos]){ while(inst[pos]){ belong[st[sttop]] = pos; inst[st[sttop]] = 0; sttop--; } } } bool flag[200002]; long long f[200002]; long long G[200002]; void dfs(int pos){ for(int i=0;i<vec[pos].size();i++){ int v = vec[pos][i]; if(belong[pos] != belong[v]) continue; if(!flag[v]){ flag[v] = 1; f[v] = f[pos] + val[pos][i]; dfs(v); } else { G[belong[pos]] = gcd(G[belong[pos]], abs(f[pos] + val[pos][i] - f[v])); } } } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int u, v, w; scanf("%d%d%d",&u,&v,&w); vec[u].push_back(v), val[u].push_back(w); } for(int i=1;i<=n;i++) if(dfn[i] == 0) tarjan(i); for(int i=1;i<=n;i++){ if(!flag[i]) flag[i] = 1, dfs(i); } scanf("%d",&q); while(q--){ int V, S, T; scanf("%d%d%d",&V,&S,&T); if((T-S) % gcd(G[belong[V]], T) == 0) printf("YES\n"); else printf("NO\n"); } return 0; } ```