回忆 Tarjan 里讲的有向图 DFS 生成树:
DFS 生成树  每当通过某条边访问到一个新节点,就加入这个点和这条边,并为点记录 DFS 序(unix 时间戳)。
DFS 生成树的边的种类:
树边  DFS 生成树上的边;前向边  从某个点到它的某个子孙节点的边,产生了捷径。反向边  从某个点到它的某个祖先节点的边,产生了环;横叉边  从某个点到一个既非它子孙节点、也非它祖先节点的边,连接了两个强连通子图。对于无向图是类似的,不同点是 无向图的 DFS 树没有横叉边  ,只有前向边和反向边,而前向边都是已访问的,可以认为 无向图的 DFS 树只有反向边 ,因此非树边的端点u , v u,v u , v v v v w v − d ,   w u + d w_{v}-d,\ w_{u}+d w v  − d ,   w u  + d 
给定一个无向连通图。每次询问给出一组边,确定删除这些边后图是否连通。
令第i i i 2 i 2^i 2 i ⊕ 2 i \oplus 2^i ⊕ 2 i 0 0 0 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 ll w[N], d[N]; bool  vis[N];vector<pair<int , int >> E[N]; void  dfs (int  u, int  p)      vis[u] = 1 ;     for  (auto & [v, id] : E[u]) {         if  (v == p) continue ;         if  (!vis[v]) {             dfs (v, u);             d[u] ^= d[v];             w[id] = d[v];         }         else  if  (!w[id]) {             w[id] = rng ();             d[u] ^= w[id];             d[v] ^= w[id];         }     } } void  eachT ()      int  n = read (), m = read ();     for  (int  i = 1 ; i <= m; i++) {         int  u = read (), v = read ();         E[u].emplace_back (v, i);         E[v].emplace_back (u, i);     }     dfs (1 , 0 );     for  (int  q = read (); q--;) {         Hamel H;         bool  ok = 1 ;         for  (int  c = read (); c--;) {             int  id = read ();             if  (!H.insert (w[id])) ok = 0 ;         }         printf ("%s\n" , ok ? "Connected"  : "Disconnected" );     } } 
变式:给定n n n n n n m m m a i , b i a_i,b_i a i  , b i  a i , b i a_i,b_i a i  , b i  CF1996G. Penacony )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void  eachT ()      int  n = read (), m = read ();     vector<ull> f (n)  ;     while  (m--) {         int  a = read () - 1 , b = read () - 1 ;         ull x = rng ();         f[a] ^= x, f[b] ^= x;       }     for  (int  i = 1 ; i < n; ++i) {         f[i] ^= f[i - 1 ];           }     unordered_map<ull, int > cnt;     int  ans = 0 ;     for  (int  i = 0 ; i < n; ++i) {         ans = max (ans, ++cnt[f[i]]);     }     printf ("%d\n" , n - ans); } 
题意  给定连通无向图,可以从图中删除一条边,问删除哪些边可以使图变成一个二分图。
思路  二分图的充要条件:不存在奇环。如果图中存在奇环,我们需要删一条边破坏图中所有的奇环,那么答案是所有奇环的交集中的边。由于一个自交的奇环必然引出一个简单奇环,所以我们只需要考虑简单环。在 DFS 生成树上考虑。
定义:
本原环:只含有一条非树边的环。 覆盖:对于一条非树边,称其本原环中的树边被其覆盖。 奇边:形成的本原环是奇环的非树边。 偶边:形成的本原环是偶环的非树边。 注意到,答案边被所有奇边覆盖,如果不止一个本原奇环,那么答案边不会被偶边覆盖。因此,如果不止一个本原奇环,那么不被任何偶边覆盖,且被所有奇边覆盖的边,就是答案边。
两个特判:
原图本就为二分图:随意删除都可行; 只有一个本原奇环:唯一的非树边也是答案。 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 69 70 71 72 vector<pair<int , int >> E[N]; int  dep[N], vis[N];int  w[N];    int  val[N];  int  cnt;     int  spj;     void  dfs1 (int  u)      vis[u] = 1 ;     for  (auto & [v, id] : E[u]) {         if  (!vis[v]) {               dep[v] = dep[u] + 1 ;             w[id] = 1 ;             dfs1 (v);         }         else  if  (!w[id]) {               w[id] = 1 ;             if  (dep[u] - dep[v] & 1 ) {                   --val[u], ++val[v];               }             else  {                   ++cnt;                 ++val[u], --val[v];                   spj = id;               }         }     } } vector<int > res; void  dfs2 (int  u)      vis[u] = 1 ;     for  (auto & [v, id] : E[u]) {         if  (!vis[v]) {             dfs2 (v);             if  (val[v] == cnt) res.push_back (id);               val[u] += val[v];            }     } } void  eachT ()      int  n = read (), m = read ();     for  (int  i = 1 ; i <= m; ++i) {         int  u = read (), v = read ();         E[u].emplace_back (v, i);         E[v].emplace_back (u, i);     }     dfs1 (1 );     for  (int  i = 1 ; i <= n; ++i) vis[i] = 0 ;     dfs2 (1 );          if  (cnt == 0 ) {         printf ("%d\n" , m);         for  (int  i = 1 ; i <= m; ++i)             printf ("%d " , i);         return ;     }          if  (cnt == 1 ) {         res.push_back (spj);     }     printf ("%lld\n" , res.size ());     for  (auto & i : res) {         printf ("%d " , i);     } } 
洛谷 P1989 
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 int  n, m;cin >> n >> m; vector<pair<int , int >> edge (m); vector<int > deg (n)  ;for  (auto & [u, v] : edge) {    cin >> u >> v;     u--, v--;     deg[u]++, deg[v]++; } vector<unordered_map<int , int >> adj (n); vector<vector<int >> E (n); for  (auto & [u, v] : edge) {    if  (deg[u] > deg[v] || deg[u] == deg[v] && u < v) {         swap (u, v);     }     E[u].push_back (v);     adj[u][v] = 1 ; } ll res = 0 ; for  (int  u = 0 ; u < n; ++u) {    for  (auto & v : E[u]) {           for  (auto & w : E[v]) res += adj[u].count (w);     } } cout << res << "\n" ; 
[[…/contests/2024 牛客多校 /2024-07-18:2024 牛客暑期多校训练营 2#*B. MST|2024-07-18:2024 牛客暑期多校训练营 2]]
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 void  eachT ()      int  n = read (), m = read (), q = read ();     vector<tuple<int , int , int >> E (m);     vector<int > deg (n)  ;     for  (auto & [w, u, v] : E) {         u = read () - 1 , v = read () - 1 ;         w = read ();         ++deg[u];         ++deg[v];     }          vector<vector<pair<int , int >>> G (n);     vector<unordered_map<int , int >> adj (n + 1 );     for  (auto & [w, u, v] : E) {         if  (deg[u] > deg[v] || deg[u] == deg[v] && u < v) {             swap (u, v);         }         G[u].emplace_back (w, v);         adj[u][v] = w;     }     DSU dsu (n)  ;       while  (q--) {         int  k = read ();                  vector<int > node (k)  ;         unordered_map<int , int > vis;         for  (auto & u : node) {             u = read () - 1 ;             vis[u] = 1 ;                          dsu.p[u] = u;             dsu.siz[u] = 1 ;         }                  vector<tuple<int , int , int >> E;         for  (auto & u : node) {               for  (auto & [w, v] : G[u]) {                 if  (vis.count (v)) {                     E.emplace_back (w, u, v);                 }             }         }         sort (E.begin (), E.end ());                  ll res = 0 , cnt = 0 ;         for  (auto & [w, u, v] : E) {             if  (dsu.uno (u, v)) {                 res += w;                 cnt += 1 ;             }         }         if  (cnt != k - 1 ) res = -1 ;           printf ("%lld\n" , res);     } } 
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 void  eachT ()      int  n = read (), m = read (), q = read ();     vector<vector<int >> E (n);     vector<int > deg (n)  ;     while  (m--) {         int  u = read () - 1 , v = read () - 1 ;         E[u].push_back (v);         E[v].push_back (u);         ++deg[u];         ++deg[v];     }     queue<int > Q;     vector<int > vis (n)  ;     for  (int  i = 0 ; i < n; ++i) {         if  (deg[i] <= 10 ) Q.push (i);     }     vector<vector<int > > G (n);     while  (!Q.empty ()) {         auto  u = Q.front (); Q.pop ();         vis[u] = 1 ;         for  (auto & v : E[u]) {             if  (vis[v]) continue ;             G[u].push_back (v);             if  (--deg[v] == 10 ) Q.push (v);         }     }     vector<int > a (n) , f (n)  ;     for  (int  u = 0 ; u < n; ++u) {         a[u] = read ();         for  (auto & v : G[u]) {             f[v] += a[u];         }     }     while  (q--) {         int  op = read (), u = read () - 1 ;         if  (op == 1 ) {             int  w = read ();             a[u] += w;             for  (auto & v : G[u]) {                 f[v] += w;             }         }         else  {             int  res = f[u];             for  (auto & v : G[u]) {                 res += a[v];             }             printf ("%d\n" , res);         }     } } 
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 constexpr  int  B = 3000 , MOD = 998244353 ;int  n, m, q;cin >> n >> m >> q; vector<int > a (n)  ;for  (int  i = 0 ; i < n; ++i) {    cin >> a[i]; } vector<vector<pair<int , int >>> E (n); while  (m--) {    int  u, v, w;     cin >> u >> v >> w;     u--, v--;     E[u].push_back ({ w, v });     E[v].push_back ({ w, u }); } vector<vector<pair<int , int >>> El (n); vector<multiset<ll>> s (n); for  (int  u = 0 ; u < n; ++u) {    for  (auto & [w, v] : E[u]) {         s[u].insert (w);         if  (int (E[v].size ()) > B) {             El[u].push_back ({ w, v });           }     } } vector<int > b (q)  ;for  (int  i = q - 1 ; i >= 0 ; --i) {    cin >> b[i];     b[i]--; } vector<ll> dp (n)  ;for  (auto & u : b) {    for  (auto & [w, v] : El[u]) s[v].extract (dp[u] + w);       if  (int (E[u].size ()) <= B) {         dp[u] = 8e18 ;         for  (auto & [w, v] : E[u]) {             dp[u] = min (dp[u], dp[v] + w);         }     }     else  {         dp[u] = *s[u].begin ();     }     for  (auto & [w, v] : El[u]) s[v].insert (dp[u] + w); } ll res = 0 ; for  (int  i = 0 ; i < n; ++i) {    res += a[i] * dp[i];     res %= MOD; } cout << res << '\n' ; 
[[…/contests/2024 杭电多校 /2024-07-29:2024“钉耙编程”中国大学生算法设计超级联赛(4)|2024-07-29:2024“钉耙编程”中国大学生算法设计超级联赛(4)]]
题意  给定两个长度为n n n a 0 , a 1 , … , a n − 1 a_0,a_1,\dots,a_{n-1} a 0  , a 1  , … , a n − 1  b 0 , b 1 , … , b n − 1 b_0,b_1,\dots,b_{n-1} b 0  , b 1  , … , b n − 1  q q q k k k i i i a i a_i a i  max  ( a i , b ( i + k )   m o d   n ) \max(a_i,b_{(i+k)\bmod n}) max ( a i  , b ( i + k ) m o d n  ) a a a ∑ i = 0 n − 1 a i \sum_{i=0}^{n-1} a_i ∑ i = 0 n − 1  a i  a i , b i a_i,b_i a i  , b i  [ 1 , 1 0 9 ] [1,10^9] [ 1 , 1 0 9 ] k k k [ 0 , n ) [0,n) [ 0 , n ) 
思路  取lim  \lim lim b b b x x x 
枚举a a a lim  \lim lim i i i b ( i + k )   m o d   n b_{(i+k) \bmod n} b ( i + k ) m o d n   枚举b b b l i m l i m l i m i i i a ( i − k )   m o d   n a_{(i-k) \bmod n} a ( i − k ) m o d n  O ( x ) O(x) O ( x )  由于数据随机,考虑计算a a a lim  \lim lim i i i lim  \lim lim i + 1 i+1 i + 1 lim  \lim lim ( 1 − x n ) i + 1 \left(1-\frac{x}{n}\right)^{i+1} ( 1 − n x  ) i + 1 
∑ i ≥ 0 ( 1 − x n ) i + 1 ≈ 1 x n = n x \sum_{i \geq 0}\left(1-\frac{x}{n}\right)^{i+1} \approx \frac{1}{\frac{x}{n}}=\frac{n}{x} i ≥ 0 ∑  ( 1 − n x  ) i + 1 ≈ n x  1  = x n  
综上可得期望时间复杂度为O ( n 2 x + n x ) O(\frac{n^2}{x}+n x) O ( x n 2  + n x ) x = n x=\sqrt{n} x = n  O ( n n ) O(n \sqrt{n}) O ( n n  ) 
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 void  eachT ()      int  n, q;     cin >> n >> q;     vector<int > a (n) , b (n)  ;     ll sum = 0 ;     for  (int  i = 0 ; i < n; i++) {         cin >> a[i];         sum += a[i];     }     for  (int  i = 0 ; i < n; i++) {         cin >> b[i];     }     auto  c = b;     sort (c.begin (), c.end ());     int  lim = c[n - (int )sqrt (n)];     vector<int > A, B;     for  (int  i = 0 ; i < n; i++) {         if  (a[i] <= lim) A.push_back (i);         if  (b[i] > lim) B.push_back (i);     }     auto  sol = [&](int  x, int  y) {         if  (a[x] >= b[y]) return ;         sum -= a[x];         a[x] = b[y];         sum += a[x];     };     while  (q--) {         int  k;         cin >> k;         for  (int  j = 0 ; j < A.size ();) {             int & i = A[j];             sol (i, (i + k) % n);             if  (a[i] > lim) {                 A[j] = A.back ();                 A.pop_back ();             }             else  j++;         }         for  (auto & i : B) {             sol ((i - k + n) % n, i);         }         cout << sum << "\n" ;     } } 
[[…/ 基础算法 /BITMASK#[2023SDCPCJ. Not Another Path Query Problem](https //codeforces.com/gym/104417/problem/J )|BITMASKS]]
给定图 G G G G G G 
Input Output 5 7 YES 
Q1 只有一次询问,且这一次查询只有一条边: 
Q2 只有一次询问,且这一次查询有多条边: 
Q3 多次询问,每次查询有多条边: 
但是问题是对于正在查询的某一个边权,它可能存在在很多的查询中,那么就要 把每个查询分开讨论  了,对于包含某一个边权的某一组询问, 合并边的操作应当是临时的  ,不能影响其它询问,也就是合并这组询问里包含这一边权的边后,需要 回滚到合并前的状态  才能考虑下一组询问,这就要用到 可撤销并查集  了。
由于需要记录边的编号,不能对边排序 ,我们稍稍修改 kruskal 的代码:
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 struct  Edge  {    int  w, u, v; }; Edge E[N]; map<int , vector<int > > mp;       int  main ()      int  n = read (), m = read ();     for  (int  Eid = 1 ; Eid <= m; ++Eid) {         E[Eid].u = read (), E[Eid].v = read (), E[Eid].w = read ();         mp[E[Eid].w].push_back (Eid);     }     DSU dsu (n)  ;     ll res = 0 ;       for  (auto & [w, Ew] : mp) {           for  (int  Eid : Ew) {             if  (dsu.uno (E[Eid].u, E[Eid].v))                 res += E[Eid].w;           }     }     printf ("%lld\n" , res);     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 struct  Edge  {    int  w, u, v; }; Edge E[N]; map<int , vector<int >> mp;       map<int , vector<int >> que[N];   int  ans[N];int  main ()      int  n = read (), m = read ();     for  (int  Eid = 1 ; Eid <= m; ++Eid) {         E[Eid].u = read (), E[Eid].v = read (), E[Eid].w = read ();         mp[E[Eid].w].push_back (Eid);     }     DSU dsu (n)  ;     int  q = read ();     for  (int  qid = 1 ; qid <= q; ++qid) {         ans[qid] = 1 ;         for  (int  k = read (); k--;) {             int  Eid = read ();             que[E[Eid].w][qid].push_back (Eid);         }     }     ll res = 0 ;       for  (auto & [w, Ew] : mp) {           for  (auto & [qid, Eq] : que[w]) {               if  (!ans[qid]) continue ;                   int  h = dsu.history ();               for  (auto & Eid : Eq) {                 if  (!dsu.uno (E[Eid].u, E[Eid].v))                       ans[qid] = 0 ;               }             dsu.roll (h);           }         for  (int  Eid : Ew) {             if  (dsu.uno (E[Eid].u, E[Eid].v))                 res += E[Eid].w;           }     }     for  (int  i = 1 ; i <= q; ++i) {         printf ("%s\n" , ans[i] ? "YES"  : "NO" );     } } 
例 3  给出n n n m m m h i h_i h i  i i i j j j h j − h i h_j-h_i h j  − h i  0 0 0 q q q s s s t t t e e e 0 0 0 s s s t t t n , m , q ⩽ 2 × 1 0 5 n,m,q \leqslant 2\times 10^5 n , m , q ⩽ 2 × 1 0 5 CF1851G. Vlad and the Mountains )
问题转化为s s s t t t u u u h u − h s ⩽ e h_{u}-h_{s} \leqslant e h u  − h s  ⩽ e h u ⩽ h s + e h_{u} \leqslant h_{s} +e h u  ⩽ h s  + e h max  ⩽ h s + e h_{\max} \leqslant h_{s} +e h m a x  ⩽ h s  + e h s + e h_{s} +e h s  + e s , t s,t s , t 
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 int  n, m;cin >> n >> m; vector<int > val (n)  ;for  (int  i = 0 ; i < n; i++) {    cin >> val[i]; } vector<int > pos (n)  ;iota (pos.begin (), pos.end (), 0 );sort (pos.begin (), pos.end (), [&](const  int & u, const  int & v) {    return  val[u] < val[v]; }); vector<vector<int >> g (n); while  (m--) {    int  u, v;     cin >> u >> v;     u--, v--;     g[u].push_back (v);     g[v].push_back (u); } int  q;cin >> q; vector<array<int , 4>> que (q); for  (int  i = 0 ; i < q; i++) {    int  u, v, e;     cin >> u >> v >> e;     u--, v--;     que[i] = { e + val[u], u, v, i }; } sort (que.begin (), que.end ());DSU dsu (n)  ;vector<int > ans (q)  ;int  j = 0 ;for  (int  i = 0 ; i < n; i++) {    int  u = pos[i];     for  (; j < q && que[j][0 ] < val[u]; j++) {         ans[que[j][3 ]] |= dsu.same (que[j][1 ], que[j][2 ]);     }     for  (auto  v : g[u]) {         if  (val[v] <= val[u]) dsu.uno (u, v);     } } for  (; j < q; j++) {    ans[que[j][3 ]] |= dsu.same (que[j][1 ], que[j][2 ]); } for  (int  i = 0 ; i < q; i++) {    cout << (ans[i] ? "YES"  : "NO" ) << "\n" ; } cout << "\n" ; 
多个操作,每一个操作的影响范围是一个区间(时间区间,询问区间)。 多个询问,询问(某个时间时,某个操作后的)答案。 有n n n q q q 1 1 1 n n n n ⩽ 1 × 1 0 5 ,   q ⩽ 1 × 1 0 5 n \leqslant 1\times 10^{5},\ q \leqslant 1\times 10^{5} n ⩽ 1 × 1 0 5 ,   q ⩽ 1 × 1 0 5 
1 u v w:连一条u → v u\to v u → v w w w 2 u l r w:对于所有i ∈ [ l , r ] i\in[l,r] i ∈ [ l , r ] u → i u\to i u → i w w w 3 u l r w:对于所有i ∈ [ l , r ] i\in[l,r] i ∈ [ l , r ] i → u i\to u i → u w w w 4 l1 r1 l2 r2 w:对于所有i ∈ [ l 1 , r 1 ] , j ∈ [ l 2 , r 2 ] i\in[l_{1},r_{1}],j\in[l_{2},r_{2}] i ∈ [ l 1  , r 1  ] , j ∈ [ l 2  , r 2  ] i → j i\to j i → j w w w 无论何种最短路方法,从建图就卡住了。注意到,题给建图是区间,联想到线段树,可以利用线段树减少连边数量,从而降低复杂度。
(洛谷 P6348 Journeys )
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 #define  ls (p<<1) #define  rs (p<<1|1) #define  mid ((L+R)>>1) #define  lson ls,L,mid #define  rson rs,mid,R void  eachT ()      int  n = read (), m = read (), st = read ();     st--;     int  N = (n << 3 ) + (m << 1 );     vector<int > num (n)  ;     vector<vector<pii>> E (N);     auto  build = [&](this  auto && self, int  p, int  L, int  R) {         E[p].push_back ({ 0 , p + (n << 2 ) });            if  (R - L < 2 ) return  num[L] = p + (n << 2 ), void ();         E[p].push_back ({ 0 , ls });         E[p].push_back ({ 0 , rs });         E[ls + (n << 2 )].push_back ({ 0 , p + (n << 2 ) });         E[rs + (n << 2 )].push_back ({ 0 , p + (n << 2 ) });         self (lson), self (rson);     };     build (build, 1 , 0 , n);     st = num[st];     auto  add_f = [&](this  auto && self, int  l, int  r, int  w, int  s, int  p, int  L, int  R) {         if  (l <= L && R <= r) return  E[s].push_back ({ w, p });                      if  (l < mid) self (l, r, w, s, lson);         if  (mid < r) self (l, r, w, s, rson);     };     auto  add_s = [&](this  auto && self, int  l, int  r, int  w, int  s, int  p, int  L, int  R) {         if  (l <= L && R <= r) return  E[p + (n << 2 )].push_back ({ w, s });           if  (l < mid) self (l, r, w, s, lson);         if  (mid < r) self (l, r, w, s, rson);     };     for  (int  i = 0 ; i < m; ++i) {         int  l1 = read (), r1 = read (), l2 = read (), r2 = read ();         l1--, l2--;         add_s (add_s, l1, r1, 1 , (n << 3 ) + (i << 1 ), 1 , 0 , n);               add_f (add_f, l2, r2, 1 , (n << 3 ) + (i << 1 ), 1 , 0 , n);               add_s (add_s, l2, r2, 1 , (n << 3 ) + (i << 1  | 1 ), 1 , 0 , n);           add_f (add_f, l1, r1, 1 , (n << 3 ) + (i << 1  | 1 ), 1 , 0 , n);       }     vector<int > dis (N, 1e9 ) , vis (N)  ;     dis[st] = 0 ;     priority_queue<pii, vector<pii>, greater<>> Q;     Q.push ({ 0 , st });     while  (!Q.empty ()) {         int  u = Q.top ().second; Q.pop ();         if  (vis[u]) continue ; vis[u] = 1 ;         for  (auto & [w, v] : E[u]) {             if  (vis[v]) continue ;             if  (dis[v] > dis[u] + w) {                 dis[v] = dis[u] + w;                 Q.push ({ dis[v],v });             }         }     }     for  (int  i = 0 ; i < n; ++i) {         printf ("%d\n" , dis[num[i]] >> 1 );     } } 
给点一个n n n k k k m m m 洛谷 P5787 二分图 )
输入m m m x , y , l , r x,y,\mathscr{l},r x , y , l , r x , y x,y x , y l \mathscr{l} l r r r 
Input Output 3 3 3 Yes 
0 0 0 ( 1 , 2 ) (1,2) ( 1 , 2 ) ( 2 , 3 ) (2,3) ( 2 , 3 ) 1 1 1 Yes。1 1 1 ( 1 , 3 ) (1,3) ( 1 , 3 ) 2 2 2 No。2 2 2 ( 1 , 2 ) (1,2) ( 1 , 2 ) ( 1 , 3 ) (1,3) ( 1 , 3 ) 3 3 3 ( 2 , 3 ) (2,3) ( 2 , 3 ) Yes。
构建log  k \log k log  k 节点   p \boldsymbol{p} p 时间段  [ l , r ] \boldsymbol{[\mathscr{l},r]} [ l , r ] p p p 2 p 2p 2 p 2 p + 1 2p+1 2 p + 1 [ l , m i d ] [\mathscr{l}, \mathrm{mid}] [ l , m i d ] [ m i d + 1 , r ] [\mathrm{mid}+1, r] [ m i d + 1 , r ] 
插入每一条边,转化为在log  k \log k log  k 
从根开始递归处理。处理左子树前,连上这个节点代表的边,处理左子树后,撤销连上的边,再处理右子树。递归到叶子时,时间段即是这一时刻,用扩展域并查集判断二分图 。时间复杂度Θ ( n log  k log  n ) \Theta(n\log k\log n) Θ ( n log  k log  n ) 
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 struct  DSU  {};  #define  ls (p<<1) #define  rs (p<<1|1) #define  mid ((L+R)>>1) #define  lson ls,L,mid #define  rson rs,mid,R void  eachT ()      int  n = read (), m = read (), k = read ();     vector<vector<pii>> E (k << 2 );     auto  update = [&](this  auto && self, int  l, int  r, pii x, int  p, int  L, int  R) {         if  (l <= L && R <= r) return  E[p].push_back (x);         if  (l < mid) self (l, r, x, lson);         if  (mid < r) self (l, r, x, rson);     };     while  (m--) {         int  u = read (), v = read (), l = read (), r = read ();         if  (l == r) continue ;         update (update, l, r, { u, v }, 1 , 0 , k);     }     DSU dsu (2  * n)  ;     vector<int > ans (k)  ;     auto  solve = [&](this  auto && self, int  p, int  L, int  R) {         int  h = dsu.history ();         for  (auto & [u, v] : E[p]) {             if  (dsu.same (u, v)) return  dsu.roll (h);             dsu.uno (u, v + n);             dsu.uno (v, u + n);         }         if  (R - L == 1 ) ans[L] = 1 ;         else  self (lson), self (rson);         return  dsu.roll (h);     };     solve (1 , 0 , k);          for  (int  i = 0 ; i < k; i++) {         printf ("%s\n" , ans[i] ? "Yes"  : "No" );     } } 
给点一个n n n k k k m m m 
天才的 lazytag:递归到叶时,对所在的连通块的根节点打上一个 lazytag,表示整个连通块需要加上一个值。假设u u u v v v u , v u,v u , v v v v u u u u , v u,v u , v v v v u u u 
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 struct  DSU  {    vector<int > p, siz;     vector<ll> val;     vector<pair<int &, int >> hp, hsiz;     vector<pair<ll&, ll&>> hval;     DSU () {}     DSU (int  n) {         init (n);     }     void  init (int  n)           p.resize (n + 1 );         iota (p.begin (), p.end (), 0 );         siz.assign (n + 1 , 1 );         val.assign (n + 1 , 0 );     }     int  find (int  x)           while  (x != p[x]) x = p[x];           return  x;     }     bool  same (int  x, int  y)           return  find (x) == find (y);     }     bool  uno (int  x, int  y)           x = find (x), y = find (y);         if  (same (x, y)) return  false ;         if  (siz[x] < siz[y]) swap (x, y);           hsiz.push_back ({ siz[x], siz[x] });         siz[x] += siz[y];         hp.push_back ({ p[y], p[y] });         p[y] = x;         hval.push_back ({ val[y], val[x] });         val[y] -= val[x];         return  true ;     }     int  history ()  return  hp.size (); }       void  roll (int  h)           while  (hp.size () > h) {             hp.back ().first = hp.back ().second;             hp.pop_back ();             hsiz.back ().first = hsiz.back ().second;             hsiz.pop_back ();             hval.back ().first += hval.back ().second;             hval.pop_back ();         }     } }; #define  ls (p<<1) #define  rs (p<<1|1) #define  mid ((L+R)>>1) #define  lson ls,L,mid #define  rson rs,mid,R void  eachT ()      int  n = read (), m = read (), k = n;     vector<vector<pii>> E (k << 2 );     auto  update = [&](this  auto && self, int  l, int  r, pair<int , int > x, int  p, int  L, int  R) {         if  (l <= L && R <= r) return  E[p].push_back (x);         if  (l < mid) self (l, r, x, lson);         if  (mid < r) self (l, r, x, rson);     };     while  (m--) {         int  u = read (), v = read (), l = read (), r = read ();         l--;         if  (u > v) swap (u, v);         update (update, l, r, { u,v }, 1 , 0 , k);     }     DSU dsu (n)  ;     auto  solve = [&](this  auto && self, int  p, int  L, int  R) -> void  {         int  h = dsu.history ();         for  (auto & [u, v] : E[p]) {             if  (dsu.same (u, v)) continue ;             dsu.uno (u, v);         }         if  (R - L < 2 ) dsu.val[dsu.find (1 )] += L + 1 ;         else  self (lson), self (rson);         return  dsu.roll (h);     };     solve (1 , 0 , k);     ll res = 0 ;     for  (int  i = 1 ; i <= n; i++) {         res ^= dsu.val[i];     }     printf ("%lld\n" , res); } 
给定连通无向图,可以从图中删除一条边,问删除哪些边可以使图变成一个二分图。(CF19E. Fairy )
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 int  n = read (), m = read ();if  (m == 0 ) {    printf ("0\n" );     return ; } vector<pair<int , int >> E (m); for  (auto & [u, v] : E) {    u = read (), v = read (); } DSU dsu (n << 1 )  ;vector<int > ans; auto  solve = [&](this  auto && self, int  L, int  R) {    if  (R - L < 2 ) return  ans.push_back (L);     int  mid = L + R >> 1 ;     int  h = dsu.history (), keep = 1 ;     for  (int  i = mid; i < R; ++i) {         auto & [u, v] = E[i];         if  (dsu.same (u, v)) keep = 0 ;         dsu.uno (u, v + n);         dsu.uno (u + n, v);     }     if  (keep) self (L, mid);     dsu.roll (h);     h = dsu.history (), keep = 1 ;     for  (int  i = L; i < mid; ++i) {         auto & [u, v] = E[i];         if  (dsu.same (u, v)) keep = 0 ;         dsu.uno (u, v + n);         dsu.uno (u + n, v);     }     if  (keep) self (mid, R);     return  dsu.roll (h); }; solve (0 , m);printf ("%lld\n" , ans.size ());for  (auto  x : ans) printf ("%d " , x + 1 );printf ("\n" );
题意  给定两个强连通有向图,每个图都有恰好n n n k k k 
判断能否在原图之间添加恰好n n n 
任何添加的边的两端都位于不同的图中,且是从出点指向入点; 在生成的图中,任何一个环的长度都是k k k  思路  多数有向图的题目是暴力缩点,这题直接给出一个强连通分量,以前的方法不奏效了。
我们尝试从缩点的本质考虑。Tarjan 算法首先引入了 DFS 生成树,将边分为两类,树边和非树边,后者进一步分为前向边、反向边和横叉边。树是没有环的,对剩下的三组边分别讨论:(其中d d d 
对于反向边u → v u\to v u → v d u − d v + 1 d_{u}-d_{v}+1 d u  − d v  + 1 k k k  对于前向边和横叉边u → v u\to v u → v k k k k ∣ d u − d v + 1 k\mid d_{u}-d_{v}+1 k ∣ d u  − d v  + 1  总结:如果有一条非树边u → v u\to v u → v k ∣ d u − d v + 1 k\mid d_{u}-d_{v}+1 k ∣ d u  − d v  + 1 d u + 1 ≡ d v ( m o d k ) d_{u}+1 \equiv d_{v} \pmod k d u  + 1 ≡ d v  ( m o d k )  
对于第一张图指向第二张图的边u → v u\to v u → v d u + 1 ≡ d v + Δ ( m o d k ) d_{u}+1 \equiv d_{v}+\Delta \pmod k d u  + 1 ≡ d v  + Δ ( m o d k ) d u + 1 ≡ d v ( m o d k ) d_{u}+1 \equiv d_{v} \pmod k d u  + 1 ≡ d v  ( m o d k ) Δ \Delta Δ Δ \Delta Δ 
先统计每个深度下点的个数{ cnt  d u } \lbrace \operatorname{cnt}_{d_{u}} \rbrace { c n t d u   } d u + 1 d_{u}+1 d u  + 1 { cnt  图一, 出点 , d u + 1 } \lbrace \operatorname{cnt}_{\text{图一, 出点},d_{u}+1} \rbrace { c n t 图一  ,  出点 , d u  + 1  } d v d_{v} d v  { cnt  图二, 入点 , d v } \lbrace \operatorname{cnt}_{\text{图二, 入点},d_{v}} \rbrace { c n t 图二  ,  入点 , d v   } 循环位移相等  ,类似地,第二张图指向第一张图的边v → u v\to u v → u { cnt  图二, 出点 , d u + 1 } \lbrace \operatorname{cnt}_{\text{图二, 出点},d_{u}+1} \rbrace { c n t 图二  ,  出点 , d u  + 1  } d v d_{v} d v  { cnt  图一, 入点 , d v } \lbrace \operatorname{cnt}_{\text{图一, 入点},d_{v}} \rbrace { c n t 图一  ,  入点 , d v   } 
为了计算方便,代码中把两个数组cnt  出 \operatorname{cnt}_{出} c n t 出  cnt  入 \operatorname{cnt}_{入} c n t 入  + 1 +1 + 1 + n +n + n + n +n + n + 1 +1 + 1 
判断两个数组循环位移相等是个经典问题,倍长后哈希  即可。
时间复杂度Θ ( n ) \Theta(n) Θ ( n ) 
(哈希模板来自:cup-pyy:一种比较科学的字符串哈希实现方法 ,略有修改)
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 #include  <bits/stdc++.h>  using  namespace  std;using  ll = long  long ;using  ull = unsigned  long  long ;using  ulll = __uint128_t ;constexpr  int  N = 1e6  + 8 ;constexpr  ull mod = (1ull  << 61 ) - 1 ;mt19937_64 rnd (chrono::steady_clock::now().time_since_epoch().count())  ;uniform_int_distribution<ull> dist (mod / 2 , mod - 1 )  ;const  ull H = dist (rnd);struct  mull  {    ull x;     constexpr  mull (ull x = 0 )  : x{norm (x) } {}     constexpr  ull norm (ull x)  const  return  x >= mod ? x - mod : x; }     friend  constexpr  mull operator *(mull a, mull b) { return  ulll (a.x) * b.x % mod; }     friend  constexpr  mull operator +(mull a, mull b) { return  a.norm (a.x + b.x); }     friend  constexpr  mull operator -(mull a, mull b) { return  a.norm (a.x + mod - b.x); }     friend  constexpr  bool  operator ==(mull a, mull b) { return  a.x == b.x; }     friend  constexpr  bool  operator !=(mull a, mull b) { return  a.x != b.x; } } power[N]; struct  Hash  {    vector<int > s;     vector<mull> h;     Hash (const  vector<int >& s) : s (s) {         init (s);     }     void  init (const  vector<int >& s)           const  int  n = s.size ();         h.resize (n + 1 );         for  (int  i = 0 ; i < n; i++) {             h[i + 1 ] = h[i] * H + s[i];         }     }     mull operator () (int  l, int  r)   {         return  h[r] - h[l] * power[r - l];     } }; signed  main ()      ios::sync_with_stdio (false );     cin.tie (nullptr );     power[0 ] = 1 ;     for  (int  i = 1 ; i < N; i++) {         power[i] = power[i - 1 ] * H;     }     int  t;     cin >> t;     while  (t--) {         int  n, k;         cin >> n >> k;         int  ocnt[2 ]{};           auto  solve = [&](int  op) {             vector<int > a (n);             for  (int  i = 0 ; i < n; i++) {                 cin >> a[i];                 ocnt[op] += a[i];             }             vector<vector<int >> E (n);             int  m;             cin >> m;             while  (m--) {                 int  u, v;                 cin >> u >> v;                 u--, v--;                 E[u].push_back (v);             }             vector<int > d (n, -1 )  ;             auto  dfs = [&](this  auto && self, int  u) -> void  {                 for  (auto  v : E[u]) {                     if  (d[v] != -1 ) continue ;                     d[v] = d[u] + 1 ;                     self (v);                 }             };             dfs (0 );                                                                                                                                                                                      vector<int > cnt (2  * k)  ;             for  (int  i = 0 ; i < n; i++) {                 cnt[(d[i] + a[i]) % k] += (a[i] ^ op) ? 1  : n;             }             for  (int  i = 0 ; i < k; i++) {                 cnt[i + k] = cnt[i];               }             return  Hash (cnt);         };         auto  L = solve (0 );         auto  R = solve (1 );                  bool  ok = false ;         for  (int  i = 0 ; i < k; i++) {             if  (L (0 , k) == R (i, i + k)) {                 ok = true ;             }         }         if  (ocnt[0 ] == n || ocnt[1 ] == n) ok = true ;           if  (ocnt[0 ] + ocnt[1 ] != n) ok = false ;                cout << (ok ? "YES"  : "NO" ) << "\n" ;     }     return  0 ; } 
题意  给定一张n n n m m m i i i t i t_i t i  
如果题目有解,则一定满足:
对于每个点,与之相邻的所有点的所有目标数字必定包含了1 ∼ t i − 1 1\sim t_i-1 1 ∼ t i  − 1  对于每个点,与之相邻的所有点的所有目标数字一定不包含t i t_i t i   如果满足上述条件,则按照t i t_i t i  
时间复杂度Θ ( n ) \Theta(n) Θ ( n ) 
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 int  n = read (), m = read ();vector<vector<int >> E (n + 1 ); while  (m--) {    int  u = read (), v = read ();     E[u].push_back (v);     E[v].push_back (u); } vector<int > tim (n + 1 )  ;vector<vector<int >> vec (n + 1 ); for  (int  i = 1 ; i <= n; ++i) {    tim[i] = read ();     vec[tim[i]].push_back (i); } vector<int > ans; bool  ok = 1 ;for  (int  time = 1 ; time <= n; ++time) {    for  (auto & v : vec[time]) {         int  mex = 1 ;         unordered_map<int , bool > vis;         for  (auto & u : E[v]) vis[tim[u]] = 1 ;         while  (vis.count (mex)) ++mex;         if  (mex == tim[v]) ans.push_back (v);         else  ok = 0 ;     } } if  (!ok) printf ("-1" );else  for  (auto  it : ans) printf ("%d " , it);
题意 n n n P = ∑ i = 1 n ∑ j = 1 n d ( i , j ) P=\sum \limits_{i=1}^{n} \sum \limits_{j=1}^{n} d(i,j) P = i = 1 ∑ n  j = 1 ∑ n  d ( i , j ) i i i i   m o d   n i\bmod n i m o d n − 1 -1 − 1 L i j L_{ij} L i j  P ≤ Q P \leq Q P ≤ Q 
思路  二分。
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 int  n = read (), need = read ();vector dist (n + 1 , vector<int >(n + 1 , INTINF)) , l (n + 1 , vector<int >(n + 1 ))  ;vector dist2 (n + 1 , vector<int >(n + 1 ))  ;for  (int  i = 1 ; i <= n; ++i) {    dist[i][i] = 0 ;     for  (int  j = 1 ; j <= n; ++j)         dist[i][j] = dist[j][i] = read (); } for  (int  i = 1 ; i <= n; ++i)    for  (int  j = 1 ; j <= n; ++j)         l[i][j] = l[j][i] = read (); auto  floyd = [&] {    for  (int  k = 1 ; k <= n; ++k)         for  (int  i = 1 ; i <= n; ++i)             for  (int  j = 1 ; j <= n; ++j)                 dist2[i][j] = min (dist2[i][j], dist2[i][k] + dist2[k][j]);     ll ans = 0 ;     for  (int  i = 1 ; i <= n; ++i)         for  (int  j = 1 ; j <= n; ++j)             ans += dist2[i][j];     return  ans; }; auto  check = [&](int  x) {    for  (int  i = 1 ; i <= n; ++i) {         for  (int  j = 1 ; j <= n; ++j)             dist2[i][j] = max (l[i][j], dist[i][j] - x * 2 );     }     return  floyd () > need; }; int  lt = 0 , rt = 2e5 , mid = -1 ;while  (lt <= rt) mid = lt + rt >> 1 , check (mid) ? lt = mid + 1  : rt = mid - 1 ;rt = max (0 , rt); for  (int  i = 1 ; i <= n; ++i)    for  (int  j = 1 ; j <= n; ++j)         dist[i][j] = max (l[i][j], dist[i][j] - rt * 2 ); dist2 = dist; if  (floyd () <= need) return  void (printf ("%d\n" , rt * 2 ));for  (int  x = 0 , pos = 1 ; x <= n * 2 ; ++x, pos = (pos + 1 ) % n, pos = pos == 0  ? n : pos) {    for  (int  i = 1 ; i <= n; ++i) {         dist[i][pos] = max (l[i][pos], dist[i][pos] - 1 );         dist[pos][i] = max (l[pos][i], dist[pos][i] - 1 );     }     dist2 = dist;     if  (floyd () <= need) return  void (printf ("%d\n" , x + 1  + rt * n)); } printf ("-1" );