structKruskal { int u, v; ll w; booloperator < ( const Kruskal &oth ) const { return w < oth.w; } }; vector<Kruskal> edge;
structEdge { int v; ll w; }; vector<Edge> adj[maxn];
int up[maxn][maxlg], dep[maxn]; ll mx1[maxn][maxlg];
voiddfs( int u, int p, ll w ){ dep[u] = dep[p] + 1, up[u][0] = p; mx1[u][0] = w; for( int i = 1; i < maxlg; ++ i ) { int mid = up[u][i-1]; up[u][i] = up[mid][i-1]; mx1[u][i] = max( mx1[u][i-1], mx1[mid][i-1] ); } for( auto [v, nw] : adj[u] ) { if( v == p ) continue; dfs( v, u, nw ); } }
ll ask( int u, int v ){ if( dep[u] < dep[v] ) swap( u, v ); int diff = dep[u] - dep[v]; ll mx = -inf; for( int i = maxlg - 1; i >= 0; -- i ) { if( ( diff >> i ) & 1 ) { mx = max( mx, mx1[u][i] ); u = up[u][i]; } } if( u == v ) return mx; for( int i = maxlg - 1; i >= 0; -- i ) { if( up[u][i] == up[v][i] ) continue; mx = max({ mx, mx1[u][i], mx1[v][i] }); u = up[u][i], v = up[v][i]; } mx = max({ mx, mx1[u][0], mx1[v][0] }); return mx; }
voidsolve( ){ int n = read( ), m = read( ); for( int i = 1; i <= m; ++ i ) { int u = read( ), v = read( ); ll w = read( ); edge.push_back({ u, v, w }); }
sort( edge.begin( ), edge.end( ) ); dsu.init( n ); for( auto [u, v, w] : edge ) { if( dsu.merge( u, v ) ) { adj[u].push_back({ v, w }); adj[v].push_back({ u, w }); } }
for( int i = 1; i <= n; ++ i ) { if( dsu.find( i ) == i ) dfs( i, 0, 0 ); }
int q = read( ); for( int i = 1; i <= q; ++ i ) { int u = read( ), v = read( ); if( u == v ) cout << 0 << '\n'; elseif( !dsu.same( u, v ) ) cout << "impossible" << '\n'; else cout << ask( u, v ) << '\n'; } }
P1967:maximin 的镜像
问题
P1967 [NOIP 2013 提高组] 货车运输
给定一张带权无向图,每条道路的边权表示限重。对多组询问 (x,y),求一辆货车从 x 到 y 时能够运输的最大重量,也就是在所有 x→y 路径中最大化路径上的最小边权;若两点不可达,输出 −1。
structKruskal { int u, v; ll w; booloperator < ( const Kruskal &oth ) const { return w > oth.w; } }; vector<Kruskal> edges;
structEdge { int v; ll w; }; vector<Edge> adj[maxn];
int up[maxn][maxlg], dep[maxn]; ll mn1[maxn][maxlg];
voiddfs( int u, int p, ll w ){ up[u][0] = p, dep[u] = dep[p] + 1; mn1[u][0] = w; for( int i = 1; i < maxlg; ++ i ) { int mid = up[u][i-1]; up[u][i] = up[mid][i-1]; mn1[u][i] = min( mn1[u][i-1], mn1[mid][i-1] ); } for( auto [v, nw] : adj[u] ) { if( v == p ) continue; dfs( v, u, nw ); } }
intget_lca( int u, int v ){ if( dep[u] < dep[v] ) swap( u, v ); int diff = dep[u] - dep[v]; for( int i = maxlg - 1; i >= 0; -- i ) { if( ( diff >> i ) & 1 ) u = up[u][i]; } if( u == v ) return u; for( int i = maxlg - 1; i >= 0; -- i ) { if( up[u][i] == up[v][i] ) continue; u = up[u][i], v = up[v][i]; } return up[u][0]; }
ll get_min( int u, int anc ){ ll mn = inf; int diff = dep[u] - dep[anc]; for( int i = 0; i < maxlg; ++ i ) { if( ( diff >> i ) & 1 ) { mn = min( mn, mn1[u][i] ); u = up[u][i]; } } return mn; }
voidsolve( ){ int n = read( ), m = read( ); for( int i = 1; i <= m; ++ i ) { int u = read( ), v = read( ), w = read( ); edges.push_back({ u, v, w }); } sort( edges.begin( ), edges.end( ) ); dsu.init( n );
for( auto [u, v, w] : edges ) { if( dsu.merge( u, v ) ) { adj[u].push_back({ v, w }); adj[v].push_back({ u, w }); } }
for( int i = 1; i <= n; ++ i ) { if( dsu.find( i ) == i ) dfs( i, 0, inf ); }
int q = read( ); for( int i = 1; i <= q; ++ i ) { int u = read( ), v = read( ); if( !dsu.same( u, v ) ) { cout << -1 << '\n'; continue; } int lca = get_lca( u, v ); ll res = min( get_min( u, lca ), get_min( v, lca ) ); cout << res << '\n'; } }
structKruskal { int u, v, w, id; booloperator < ( const Kruskal &oth ) const { return w < oth.w; } }; vector<Kruskal> edges;
structEdge { int v; ll w; }; vector<Edge> adj[maxn];
ll mst;
int up[maxn][maxlg], dep[maxn]; ll mxw[maxn][maxlg];
voiddfs( int u, int p, ll w ){ up[u][0] = p, dep[u] = dep[p] + 1; mxw[u][0] = w; for( int i = 1; i < maxlg; ++ i ) { int mid = up[u][i-1]; up[u][i] = up[mid][i-1]; mxw[u][i] = max( mxw[u][i-1], mxw[mid][i-1] ); } for( auto [v, w] : adj[u] ) { if( v == p ) continue; dfs( v, u, w ); } }
ll ask( int u, int v ){ if( dep[u] < dep[v] ) swap( u, v ); ll res = 0; int diff = dep[u] - dep[v]; for( int i = maxlg - 1; i >= 0; -- i ) { if( ( diff >> i ) & 1 ) { res = max( res, mxw[u][i] ); u = up[u][i]; } } if( u == v ) return res; for( int i = maxlg - 1; i >= 0; -- i ) { if( up[u][i] == up[v][i] ) continue; res = max({ res, mxw[u][i], mxw[v][i] }); u = up[u][i], v = up[v][i]; } res = max({ res, mxw[u][0], mxw[v][0] }); return res; }
voidsolve( ){ int n = read( ), m = read( ); for( int i = 1; i <= m; ++ i ) { int u = read( ), v = read( ), w = read( ); edges.push_back({ u, v, w, i }); } vector<Kruskal> ori_edges = edges; sort( edges.begin( ), edges.end( ) ); dsu.init( n ); for( auto [u, v, w, id] : edges ) { if( dsu.merge( u, v ) ) { mst += w; adj[u].push_back({ v, w }); adj[v].push_back({ u, w }); } } dfs( 1, 0, 0 ); for( auto [u, v, w, id] : ori_edges ) { cout << mst + w - ask( u ,v ) << '\n'; } }
voiddfs( int u, int p, ll w ){ dep[u] = dep[p] + 1, up[u][0] = p; mx1[u][0] = w, mx2[u][0] = -1; for( int i = 1; i < maxlg; ++ i ) { int mid = up[u][i-1]; up[u][i] = up[mid][i - 1]; mx1[u][i] = mx1[u][i - 1]; mx2[u][i] = mx2[u][i - 1]; updval( mx1[u][i], mx2[u][i], mx1[mid][i-1], mx2[mid][i-1] ); } for( auto [v, nw] : adj[u] ) { if( v == p ) continue; dfs( v, u, nw ); } }
intget_lca( int u, int v ){ if( dep[u] < dep[v] ) swap( u, v ); int diff = dep[u] - dep[v]; for( int i = 0; i < maxlg; ++ i ) { if( ( diff >> i ) & 1 ) u = up[u][i]; } if( u == v ) return u; for( int i = maxlg - 1; i >= 0; -- i ) { if( up[u][i] == up[v][i] ) continue; u = up[u][i], v = up[v][i]; } return up[u][0]; }
pair< ll, ll > query( int u, int anc ){ ll res1 = -1, res2 = -1; int diff = dep[u] - dep[anc]; for( int i = 0; i < maxlg; ++ i ) { if( ( diff >> i ) & 1 ) { updval( res1, res2, mx1[u][i], mx2[u][i] ); u = up[u][i]; } } return { res1, res2 }; }
for( int i = 1; i <= m; ++ i ) { int u = read( ), v = read( ); ll w = read( ); edge.push_back({ u, v, w, 0 }); } sort( edge.begin( ), edge.end( ) );
dsu.init( n ); int cnt = 0; mst = 0;
for( int i = 0; i < m; ++ i ) { auto &[u, v, w, used] = edge[i]; if( dsu.merge( u, v ) ) { mst += w; used = 1; cnt ++; adj[u].push_back({ v, w }); adj[v].push_back({ u, w }); if( cnt == n - 1 ) break; } }
voidsolve( ){ int n = read( ), m = read( ), k = read( );
for( int i = 1; i <= m; ++ i ) { int u = read( ), v = read( ), c = read( ); if( c == 0 ) edge0.push_back({ u, v, c, 0 }); else edge1.push_back({ u, v, c, 0 }); }
dsu.init( n ); int tot = 0, mn = 0;
for( auto [u, v, c, need] : edge1 ) { if( dsu.merge( u, v ) ) tot ++; }
for( auto &[u, v, c, need] : edge0 ) { if( dsu.merge( u, v ) ) { need = true; mn ++, tot ++; } }
if( tot != n - 1 || mn > k ) returnfail( );
dsu.init( n ); int cnt = 0;
for( auto [u, v, c, need] : edge0 ) { if( !need ) continue; if( dsu.merge( u, v ) ) { cnt ++; ans.push_back({ u, v, c, need }); } }
for( auto [u, v, c, need] : edge0 ) { if( cnt == k ) break; if( need ) continue; if( dsu.merge( u, v ) ) { cnt ++; ans.push_back({ u, v, c, need }); } } if( cnt != k ) returnfail( );
for( auto [u, v, c, need] : edge1 ) { if( dsu.merge( u, v ) ) { ans.push_back({ u, v, c, need }); } } if( ans.size( ) != n - 1 ) returnfail( );
for( auto [u, v, c, need] : ans ) { cout << u << ' ' << v << ' ' << c << '\n'; } }
P2619:不仅要恰好 k 条,还要最小
问题
P2619 [国家集训队] Tree I
给定一张连通带权无向图,每条边为黑色或白色。要求选择一棵生成树,使其中 恰好有 need 条白边,并在满足数量限制的所有生成树中最小化原边权总和。题目保证有解。
数据规模:V≤5×104,E≤105,边权属于 [1,100];顶点从 0 开始编号。
本题与上题的区别在于:不只是要凑出 need 条白边,还要在所有满足数量限制的生成树中继续最小化总边权。
intkruskal( int N ){ dsu.init( N ); sort( edge.begin( ), edge.end( ), cmp ); mst = 0; int white = 0; int cnt = 0; // 已选边数 for( auto [u, v, col, w] : edge ) { if( dsu.merge( u, v ) ) { mst += w; ++ cnt; if( col == 0 ) ++ white; if( cnt == N - 1 ) break; } } return white; }
boolcheck( int mid ){ for( int i = 0; i < m; ++ i ) { if( edge[i].col == 0 ) edge[i].w += mid; } bool ok = ( kruskal( n ) >= need ); for( int i = 0; i < m; ++ i ) { if( edge[i].col == 0 ) edge[i].w -= mid; } return ok; }
voidsolve( ){ n = read( ), m = read( ), need = read( );
for( int i = 1; i <= m; ++ i ) { int u = read( ) + 1, v = read( ) + 1; int w = read( ), c = read( ); edge.push_back({ u, v, c, w }); }
int l = -101, r = 101, ans = 0; while( l <= r ) { int mid = ( l + r ) >> 1; if( check( mid ) ) ans = mid, l = mid + 1; else r = mid - 1; } check( ans ); cout << mst - need * ans << '\n'; }
P5633:限制一个点的度数
问题
P5633 最小度限制生成树
给定一张带权无向图和指定顶点 s,要求找一棵总边权最小的生成树,同时满足 s 在这棵生成树中的度数 恰好为 k。如果不存在满足条件的生成树,则输出 Impossible。
数据规模:n≤5×104,m≤5×105,1≤k≤100,0≤w≤3×104。
这题与上题非常像,区别只在于:前面“特殊边”是由颜色定义的,而这里把所有与 s 相连的边看成特殊边即可。
生成树中 s 的度数恰好为 k,也就等价于:恰好选择 k 条与 s 相连的边。
不过在二分以前还要先判断这个度数是否可行。
先完全不使用与 s 相连的边,只用其他边尽可能合并。设此时还剩 block 个连通块,那么至少需要 block-1 条与 s 相连的边才能把这些部分接起来,因此这给出了 s 度数的下界。
反过来,只使用与 s 相连的边做森林,最多能够成功加入多少条,就给出了度数上界。先判断 k 是否落在这个可行区间里,再进入后面的 WQS 二分。
voidsolve( ){ n = read( ), m = read( ); s = read( ), k = read( );
for( int i = 1; i <= m; ++ i ) { int u = read( ), v = read( ); ll w = read( ); if( u == s || v == s ) edgeA.push_back({ u, v, w, 1 }); else edgeB.push_back({ u, v, w, 0 }); }
voidsolve( ){ int n = read( ); int mx = -1; for( int i = 1; i <= n; ++ i ) { int x = read( ); pos[x] = i; mx = max( mx, x ); }
dsu.init( n );
ll ans = 0; for( int d = mx; d >= 1; -- d ) { int cur = 0; for( int val = d; val <= mx; val += d ) { int nxt = pos[val]; if( !nxt ) continue; if( !cur ) cur = nxt; elseif( dsu.merge( cur, nxt ) ) { ans += d; } } } cout << ans << '\n'; }