引言
在信息竞赛中会有这样一类问题,其最终的目标是维护一些变量之间的关系(比如说相等与不等,变量之间的差),对于这类问题的不同变种,我们有不同的方法进行解决。
阅读本文前,我们假设读者已经掌握了如下内容:并查集模板、深度优先搜索、负环判定方法。
维护相等与不相等关系
变量取值任意
模板
给定 n n n 个变量 x 1 , x 2 , … , x n x_1,x_2,\ldots,x_n x 1 , x 2 , … , x n ,m m m 组关系。每组关系形如 x i = x j x_i=x_j x i = x j 或 x i ≠ x j x_i\ne x_j x i = x j 。请求出是否存在一组合法解。
解法
对于这个问题,一般解法是利用并查集:我们进行离线处理,先将 所有形如 x i = x j x_i=x_j x i = x j 的关系的 ( i , j ) (i,j) ( i , j ) 合并到一个集合中,然后 再对于所有 x i ≠ x j x_i\ne x_j x i = x j 的进行检查,如果对应的 ( i , j ) (i,j) ( i , j ) 已经被划分到了一个集合中,说明不存在合法解,否则就存在合法解。
时间复杂度 O ( n + m ) O(n+m) O ( n + m ) 。
习题
[NOI2015] 程序自动分析
同模板,注意先离散化一次。
参考代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <bits/stdc++.h> using namespace std;const int N = 2e5 +5 ;int fa[N];int find (int x) { return fa[x]==x?x:fa[x]=find (fa[x]); }void merge (int i, int j) { int fi=find (i), fj=find (j); fa[fi]=fj; }int main () { int T;scanf ("%d" ,&T); while (T--) { int n;scanf ("%d" ,&n); vector<pair<int , int >> eq, neq; vector<int > m; for (int i=0 ;i<n;i++) { int a,b,c; scanf ("%d%d%d" ,&a,&b,&c); if (c==1 )eq.push_back (make_pair (a, b)); else neq.push_back (make_pair (a, b)); m.push_back (a);m.push_back (b); } sort (m.begin (), m.end ()); m.erase (unique (m.begin (), m.end ()), m.end ()); int len = m.size (); map<int ,int > mp; for (int i=0 ;i<len;i++) { mp[m[i]]=i; } for (int i=0 ;i<len;i++) { fa[i]=i; } for (auto [a,b]:eq) { merge (mp[a],mp[b]); } bool flag = 1 ; for (auto [a,b]:neq) { if (find (mp[a])==find (mp[b])) { flag = 0 ; break ; } } if (flag)puts ("YES" ); else puts ("NO" ); } return 0 ; }
变量取值为 {0,1}
模板
给定 n n n 个变量 x 1 , x 2 , … , x n x_1,x_2,\ldots,x_n x 1 , x 2 , … , x n ,每个变量取值为 {0,1} \textbf{\{0,1\}} {0,1} 。m m m 组关系。每组关系形如 x i = x j x_i=x_j x i = x j 或 x i ≠ x j x_i\ne x_j x i = x j 。请求出是否存在一组合法解。
(等价变形)有 n n n 个人,m m m 组关系,每组关系为两人是朋友或是敌人。朋友的朋友是朋友,朋友的敌人是敌人,敌人的敌人是朋友,敌人的朋友是朋友。请确定这些关系是否矛盾。
为什么等价?
存在这个等价变形,关键在于这里的等式具有一些特殊的性质。
普通等式的都有的性质:
若 a = b , b = c a=b,b=c a = b , b = c ,则 a = c a=c a = c ;
若 a = b , b ≠ c a=b,b\ne c a = b , b = c ,则 a ≠ c a\ne c a = c ;
若 a ≠ b , b = c a\ne b,b=c a = b , b = c ,则 a ≠ c a\ne c a = c ;
{ 0 , 1 } \{0,1\} { 0 , 1 } 下特殊性质:
若 a ≠ b , b ≠ c a\ne b,b\ne c a = b , b = c ,则 a = c a=c a = c 。(普通等式没有!)
因此就与上面的朋友和敌人关系等价。
解法①——深度优先搜索法
对每个节点开始进行深度优先搜索,并同时维护每个节点的取值。如果从这个节点开始访问到了一个没有确定值的节点,那么就用关系确定出其取值。如果遇到了一个已经确定了值的节点,那么就用关系判定是否矛盾,矛盾就直接返回。
解法②——拓展并查集法
总所周知,正常的并查集只能维护是否在同一集合中,无法维护这样的关系。但是,通过一种叫做拓展并查集的技巧,我们就可以维护这种敌人和朋友关系。
具体来说,将每个变量 u u u 拓展为两个节点 u , u ′ u,u' u , u ′ (称为原点和拓展点)。我们定义 所有只要与节点 u u u 连通的原点 是 u u u 的朋友,所有只要与节点 u u u 联通的原点是 u ′ u' u ′ 的敌人。注意:这是我们人为定义出来的关系,后面可以看到,这样定义恰好能解决这个问题。
对于朋友关系 ( u , v ) (u,v) ( u , v ) ,我们需要将 ( u , v ) (u,v) ( u , v ) 和 ( u ′ , v ′ ) (u',v') ( u ′ , v ′ ) 合并(代表我的朋友是你的朋友,我的敌人是你的敌人),对于敌人关系 ( u , v ) (u,v) ( u , v ) ,我们只需要将 ( u , v ′ ) (u,v') ( u , v ′ ) 和 ( u ′ , v ) (u',v) ( u ′ , v ) 合并即可(代表我的敌人是你的朋友,你的朋友是我的敌人)。
举个例子,假设有三个节点,则初始并查集如下图左。
我们假设现在 ( 1 , 2 ) (1,2) ( 1 , 2 ) 是敌人,那么之后的并查集如下图中。此时我们可以看到,拓展点 1 ′ 1' 1 ′ 和原点 2 2 2 连通,根据定义,我们知道了 2 2 2 是 1 1 1 的敌人。同理我们得知 1 1 1 是 2 2 2 的敌人。
我们再假设现在 ( 1 , 3 ) (1,3) ( 1 , 3 ) 是朋友,那么我们将 ( 1 , 3 ) , ( 1 ′ , 3 ′ ) (1,3),(1',3') ( 1 , 3 ) , ( 1 ′ , 3 ′ ) 连接,如下图右。此时我们发现,拓展点 2 ′ 2' 2 ′ 和原点 1 , 3 1,3 1 , 3 连通,因此 2 2 2 是 1 , 3 1,3 1 , 3 的敌人。
不难发现,这样的定义是完备的,因此可以完美解决这个问题。
这样定义后,就可以判断新加入的一组关系是否和前面的关系有矛盾:
新加入朋友关系 ( u , v ) (u,v) ( u , v ) 是否有矛盾?
若此时 u ′ u' u ′ 和 v v v 在同一个集合中,那么就有矛盾(并且此时 u u u 和 v ′ v' v ′ 也一定 在一个集合中);
新加入敌人关系 ( u , v ) (u,v) ( u , v ) 是否有矛盾?
若此时 u u u 和 v v v 在同一个集合中,那么就有矛盾(并且此时 u ′ u' u ′ 和 v ′ v' v ′ 也一定 在一个集合中)。
在实践中,我们将所有拓展点 u u u 的编号记为 u + n u+n u + n 。
解法③——带权并查集法
上面一个方法可能有一点难以理解,实践中还有一种更好用的方法来改进并查集,称作带权并查集。
在带权并查集中,我们对每一个节点额外维护一个权值 h h h ,作为和它的祖先 的关系。
在这个情况下,我们将和祖先相同的节点权值置为 0 0 0 ,和祖先不同的节点权值置为 1 1 1 。这个权值是动态维护 的,它随着祖先的改变而改变。
因此,我们在路径压缩和合并集合时需要对这个权值进行修改。这就是带权并查集全部题目的核心。
路径压缩
画图进行分析,我们知道了新的 h [ u ] h[u] h [ u ] 为 h [ f a [ u ] ] ⊕ h [ u ] h[fa[u]] \oplus h[u] h [ f a [ u ]] ⊕ h [ u ] 。
合并集合
同样画图进行分析。我们假设 u u u 作为最后合并集合的根,然后画图可以推导出新的 h h h 。
那么,如何判定新加入关系是否矛盾呢?
假设现在的关系是 ( u , v ) (u,v) ( u , v ) 之间的关系。
若目前 ( u , v ) (u,v) ( u , v ) 不在同一个集合当中,那么这个关系一定不矛盾,直接合并集合即可。
若目前 ( u , v ) (u,v) ( u , v ) 在同一集合当中,那么这个关系就可以根据 u , v u,v u , v 与它们共同的根的关系 h [ u ] h[u] h [ u ] 和 h [ v ] h[v] h [ v ] 进行判断是否矛盾。
总结
这三种方法中,第一种深度优先搜索必须全部读入关系后离线 处理,后面两种方法都可以进行在线 处理。平常更推荐采用带权并查集,理解更简单,并可以维护更多信息。
习题
[NOIP2010 提高组] 关押罪犯
考虑如何求出这个影响力。根据贪心,我们应该先优先满足影响力大的罪犯,直到出现了矛盾,然后矛盾的这组关系的影响力就是最小的影响力。
参考代码(深度优先搜索):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <bits/stdc++.h> using namespace std;const int N = 2e4 +5 ;const int M = 1e5 +5 ;int n,m;struct Node { int u,v,w; bool operator >(const Node& o)const { return w>o.w; } }; Node a[M]; vector<int > G[N];int color[N];bool dfs (int u) { for (int v:G[u]){ if (color[v]==-1 ){ color[v]=color[u]^1 ; if (!dfs (v))return 0 ; } else if (color[v]!=(color[u]^1 )){ return 0 ; } } return 1 ; }bool check (int cnt) { for (int i=1 ;i<=n;i++)G[i].clear (); for (int i=0 ;i<cnt;i++){ G[a[i].u].push_back (a[i].v); G[a[i].v].push_back (a[i].u); } memset (color,-1 ,sizeof (color)); for (int i=1 ;i<=n;i++){ if (color[i]==-1 ){ color[i]=0 ; if (!dfs (i))return 0 ; } } return 1 ; }int main () { scanf ("%d%d" ,&n,&m); for (int i=0 ;i<m;i++){ scanf ("%d%d%d" ,&a[i].u,&a[i].v,&a[i].w); } sort (a,a+m,greater <Node>()); int l = 0 , r= m; while (l<r){ int mid = (l+r+1 )>>1 ; if (check (mid))l=mid; else r=mid-1 ; } printf ("%d\n" ,a[l].w); }
参考代码(拓展并查集):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <bits/stdc++.h> using namespace std;const int N = 2e4 +5 ;const int M = 1e5 +5 ;int n,m;struct Node { int u,v,w; bool operator >(const Node& o)const { return w>o.w; } }; Node a[M];int fa[N*2 ];int find (int x) { return x==fa[x]?x:fa[x]=find (fa[x]); }void unite (int u,int v) { fa[find (u)]=find (v); }int main () { scanf ("%d%d" ,&n,&m); for (int i=0 ;i<m;i++){ scanf ("%d%d%d" ,&a[i].u,&a[i].v,&a[i].w); } sort (a,a+m,greater <Node>()); for (int i=1 ;i<=2 *n;i++){ fa[i]=i; } for (int i=0 ;i<m;i++){ if (find (a[i].u)==find (a[i].v)){ printf ("%d\n" ,a[i].w); return 0 ; } unite (a[i].u+n,a[i].v); unite (a[i].v+n,a[i].u); } puts ("0" ); return 0 ; }
参考代码(带权并查集):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <bits/stdc++.h> using namespace std;const int N = 2e4 +5 ;const int M = 1e5 +5 ;int n,m;struct Node { int u,v,w; bool operator >(const Node& o)const { return w>o.w; } }; Node a[M];int fa[N];int h[N];int find (int x) { if (fa[x]==x)return x; int p = find (fa[x]); h[x] = h[x]^h[fa[x]]; return fa[x]=p; }void unite (int u,int v) { int fu=find (u),fv=find (v); fa[fv]=fu; h[fv]=h[u]^h[v]^1 ; }int main () { scanf ("%d%d" ,&n,&m); for (int i=0 ;i<m;i++){ scanf ("%d%d%d" ,&a[i].u,&a[i].v,&a[i].w); } sort (a,a+m,greater <Node>()); for (int i=1 ;i<=n;i++){ fa[i]=i; h[i]=0 ; } for (int i=0 ;i<m;i++){ if (find (a[i].u)==find (a[i].v) && h[a[i].u]==h[a[i].v]){ printf ("%d\n" ,a[i].w); return 0 ; } else { unite (a[i].u,a[i].v); } } puts ("0" ); return 0 ; }
维护变量的差
正常意义下差
模板
给定 n n n 个变量 x 1 , x 2 , … , x n x_1,x_2,\ldots,x_n x 1 , x 2 , … , x n ,m m m 组关系。每组关系形如 x i − x j = d x_i-x_j=d x i − x j = d 。请求出是否存在一组合法解。
解法
这个问题也有两个方法,一种类似于上面的深度优先搜索法,只需要将维护关系时的异或运算改为加法即可。
另一种改编自上面的带权并查集,只需要重新推出路径压缩和合并的式子即可。按照上文画图分析,很容易可以推出,在此略去(详见代码)。
在这个问题上,两种方法仍然和上面说的一样,深度优先搜索只能离线处理,带权并查集可以在线处理。
习题
[CF1850H] The Third Letter
参考代码(深度优先搜索):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <bits/stdc++.h> using namespace std;using ll=long long ;const int N=2e5 +5 ; ll pos[N];struct Edge { int v,w; }; vector<Edge> G[N];bool ok;void dfs (int u) { if (pos[u]==(ll)1e18 ){ pos[u]=0 ; } for (auto [v,w]:G[u]){ if (pos[v]!=(ll)1e18 ){ if (pos[v]!=pos[u]+w)ok=0 ; } else { pos[v]=pos[u]+w; dfs (v); } } }void solve () { int n,m;scanf ("%d%d" ,&n,&m); for (int i=1 ;i<=n;i++){ G[i].clear (); } for (int i=0 ;i<m;i++){ int u,v,w; scanf ("%d%d%d" ,&u,&v,&w); G[u].push_back ({v,w}); G[v].push_back ({u,-w}); } for (int i=1 ;i<=n;i++){ pos[i]=(ll)1e18 ; } ok=1 ; for (int i=1 ;i<=n;i++){ dfs (i); } if (ok)puts ("YES" ); else puts ("NO" ); }int main () { int T;scanf ("%d" ,&T); while (T--){ solve (); } return 0 ; }
参考代码(带权并查集):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <bits/stdc++.h> using namespace std;using ll=long long ;const int N=2e5 +5 ;int fa[N]; ll h[N];int find (int x) { if (fa[x]==x)return x; int p=find (fa[x]); h[x]=h[x]+h[fa[x]]; return fa[x]=p; }void unite (int u,int v,ll dis) { int fu=find (u),fv=find (v); fa[fv]=fu; h[fv]=h[u]-h[v]-dis; }void solve () { int n,m;scanf ("%d%d" ,&n,&m); for (int i=1 ;i<=n;i++){ fa[i]=i; h[i]=0 ; } bool ok=1 ; for (int i=0 ;i<m;i++){ int u,v,w; scanf ("%d%d%d" ,&u,&v,&w); if (find (u)==find (v)){ if (h[u]-h[v]!=w){ ok=0 ; } } else { unite (u,v,w); } } if (ok)puts ("YES" ); else puts ("NO" ); }int main () { int T;scanf ("%d" ,&T); while (T--){ solve (); } return 0 ; }
模意义下差
模板
给定模数 M M M ,n n n 个变量 x 1 , x 2 , … , x n x_1,x_2,\ldots,x_n x 1 , x 2 , … , x n ,变量取值为 { 0 , 1 , … , M − 1 } \{0,1,\ldots,M-1\} { 0 , 1 , … , M − 1 } 。给出 m m m 组关系。每组关系形如 x i − x j ≡ d ( m o d M ) x_i-x_j\equiv d \pmod M x i − x j ≡ d ( mod M ) 。请求出是否存在一组合法解。
解法
事实上,这个问题是我们在 2.2 中讨论问题的强化版。2.2 中问题可看作是 M = 2 M=2 M = 2 时的一个特例。
因此,三个方法仍然都可以采用。
深度优先搜索和带权并查集都和 2.2 中内容相同,只需修改对权值的维护操作即可,可自行推导(详见代码)。
拓展并查集此时要将每个节点拆分成 M M M 个(如图),对于关系 x A − x B ≡ d ( m o d M ) x_A-x_B\equiv d\pmod M x A − x B ≡ d ( mod M ) ,我们需要将 i = 0 , 1 , … , M − 1 , ( A i , B ( i + d m o d M ) ) i=0,1,\ldots,M-1, (A_i,B_{(i+d\bmod M)}) i = 0 , 1 , … , M − 1 , ( A i , B ( i + d mod M ) ) 这样 M M M 对点合并到同一个集合中。
对于判断条件是否与前面的冲突,我们需要找出是否存在 ( A i , B j ) (A_i,B_j) ( A i , B j ) 在同一个集合中,并且 i − j ≢ d ( m o d M ) i-j\not\equiv d\pmod M i − j ≡ d ( mod M ) ,直接枚举所有 M − 1 M-1 M − 1 对其他的点对即可。
习题
[NOI2001] 食物链
考虑三种类型 A , B , C A,B,C A , B , C 分别用 0 , 1 , 2 0,1,2 0 , 1 , 2 表示。此时,若 u u u 被 v v v 吃,那么两个动物类型在模意义下的差为 2 2 2 ;若 u u u 吃 v v v ,那么两个动物类型在模意义下的差为 1 1 1 ;若 u u u 和 v v v 是同类,那么两个动物类型在模意义下的差为 0 0 0 。因此我们就成功将这个问题转化为了上面的模板。由于本题不适合离线,故不给出使用深度优先搜索的示例代码。
参考代码(拓展并查集):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <bits/stdc++.h> using namespace std;const int N=5e4 +5 ;int fa[N*3 ]; int find (int x) { return fa[x]==x?x:fa[x]=find (fa[x]); }void unite (int u,int v) { fa[find (u)]=find (v); }int main () { int n,k;scanf ("%d%d" ,&n,&k); for (int i=1 ;i<=3 *n;i++)fa[i]=i; int cnt = 0 ; for (int i=0 ;i<k;i++){ int a,u,v; scanf ("%d%d%d" ,&a,&u,&v); if (u>n || v>n){ cnt++; continue ; } if (a==2 && u==v){ cnt++; continue ; } if (a==1 ){ if (find (u+n)==find (v)||find (u+n+n)==find (v)){ cnt++; } else { unite (u,v); unite (u+n,v+n); unite (u+n+n,v+n+n); } } else { if (find (u)==find (v) || find (u)==find (v+n)){ cnt++; } else { unite (u,v+n+n); unite (u+n,v); unite (u+n+n,v+n); } } } printf ("%d\n" ,cnt); return 0 ; }
参考代码(带权并查集):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <bits/stdc++.h> using namespace std;const int N=5e4 +5 ;int fa[N]; int h[N]; int find (int x) { if (fa[x]==x)return x; int p=find (fa[x]); h[x]=(h[x]+h[fa[x]])%3 ; return fa[x]=p; }void unite (int u,int v,int val) { int fu=find (u),fv=find (v); fa[fv]=fu; h[fv]=(-h[v]-val+h[u]+42 )%3 ; }int main () { int n,k;scanf ("%d%d" ,&n,&k); for (int i=1 ;i<=n;i++)fa[i]=i; int cnt = 0 ; for (int i=0 ;i<k;i++){ int a,u,v; scanf ("%d%d%d" ,&a,&u,&v); if (u>n || v>n){ cnt++; continue ; } if (a==2 && u==v){ cnt++; continue ; } if (a==1 ){ if (find (u)==find (v)){ if (h[u]!=h[v])cnt++; } else unite (u,v,0 ); } else { if (find (u)==find (v)){ if ((h[u]-h[v]+42 )%3 !=2 )cnt++; } else unite (u,v,2 ); } } printf ("%d\n" ,cnt); return 0 ; }
维护不等式关系
模板
(差分约束)给定 n n n 个变量 x 1 , x 2 , … , x n x_1,x_2,\ldots,x_n x 1 , x 2 , … , x n ,m m m 组关系。每组关系形如 x i − x j ≤ d x_i-x_j\le d x i − x j ≤ d 。请求出是否存在一组合法解。
解法
我们可以把每个变量 x i x_i x i 看做图中的一个结点,对于每个约束条件 x i − x j ≤ c k x_i-x_j\le c_k x i − x j ≤ c k ,从结点 j j j 向结点 i i i 连一条长度为 c k c_k c k 的有向边。
设 d i s t 0 = 0 dist_0=0 d i s t 0 = 0 并向每一个点连一条权重为 0 0 0 边,跑单源最短路,若图中存在负环,则给定的差分约束系统无解,否则,x i = d i s t i x_i=dist_i x i = d i s t i 为该差分约束系统的一组解(证明略)。其余解都是该组解加上一个常数。[1]
习题
[洛谷P1993] 小K的农场
对于 x a − x b ≥ c x_a-x_b\ge c x a − x b ≥ c ,转化为 x b − x a ≤ − c x_b-x_a\le -c x b − x a ≤ − c ;对于 x a − x b ≤ c x_a-x_b\le c x a − x b ≤ c ,无需转化;对于 x a = x b x_a=x_b x a = x b ,转化为 x a − x b ≤ 0 x_a-x_b\le 0 x a − x b ≤ 0 且 x b − x a ≤ 0 x_b-x_a\le 0 x b − x a ≤ 0 。这样转化显然与原命题等价,因此我们就转化为了模板问题。
参考代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <bits/stdc++.h> using namespace std;const int N = 5e3 +5 ; vector<pair<int ,int >> G[N];int d[N]; int cnt[N]; bool inqueue[N];int main () { int n,m; scanf ("%d%d" ,&n,&m); for (int i=0 ;i<m;i++){ int x;scanf ("%d" ,&x); if (x==1 ){ int a,b,c; scanf ("%d%d%d" ,&a,&b,&c); G[a].push_back ({b,-c}); } else if (x==2 ){ int a,b,c; scanf ("%d%d%d" ,&a,&b,&c); G[b].push_back ({a,c}); } else { int a,b; scanf ("%d%d" ,&a,&b); G[a].push_back ({b,0 }); G[b].push_back ({a,0 }); } } for (int i=1 ;i<=n;i++){ G[0 ].push_back ({i,0 }); } memset (d,0x3f ,sizeof (d)); d[0 ]=0 ; queue<int > q; q.push (0 );inqueue[0 ]=1 ; while (q.size ()){ int u=q.front ();q.pop ();inqueue[u]=0 ; for (auto [v,w]:G[u]){ if (d[v]>d[u]+w){ d[v]=d[u]+w; cnt[v]=cnt[u]+1 ; if (cnt[v]>=n+1 ){ puts ("No" ); return 0 ; } if (!inqueue[v]){ inqueue[v]=1 ; q.push (v); } } } } puts ("Yes" ); return 0 ; }
[1]. OI Wiki - 差分约束