int g[maxn][maxn], d[maxn], N, M;
bool vis[maxn];
int prim() {
int res = 0;
for (int i = 1; i <= N; i++) d[i] = INF;
for (int i = 0; i < N; i++) {
int t = -1;
for (int j = 1; j <= N; j++) {
if (!vis[j] && (t == -1 || d[t] > d[j])) {
t = j;
}
}
if (i && d[t] == INF) return INF;
if (i) res += d[t];
vis[t] = true;
for (int j = 1; j <= N; j++) d[j] = min(d[j], g[t][j]);
}
return res;
}
(2)堆优化版prim算法( O ( m ∗ l o g n ) O(m * logn) O(m∗logn))
int N, M, int p[maxn];
struct e{
int u, v, w;
bool operator <(const e& rhe) const{
return w < rhe.w;
}
}edges[maxm];
void init(int N) {
for (int i = 1; i <= N; i++) p[i] = i;
}
int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
void unite(int a, int b) {
if (find(a) == find(b)) return;
p[find(a)] = find(b);
}
int kru() {
init(N);
int cnt = 0, res = 0;
sort(edges, edges + M);
for (int i = 0; i < M; i++) {
auto p = edges[i];
if (find(p.u) != find(p.v)) {
unite(p.u, p.v);
res += p.w;
cnt++;
}
}
if (cnt == N - 1) return res;
return -1;
}
#include
#include
using namespace std;
const int maxn = 6010;
int p[maxn], sz[maxn], N, M;
void init(int N)
{
for (int i = 1; i <= N; i++)
{
p[i] = i, sz[i] = 1;
}
}
int find(int x)
{
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
void unite(int a, int b)
{
if (find(a) == find(b)) return;
sz[find(b)] += sz[find(a)];
p[find(a)] = find(b);
}
struct P
{
int u, v, cost;
bool operator<(const P& rhp)
{
return cost < rhp.cost;
}
}G[maxn];
void solve()
{
init(N);
sort(G, G + M);
int ans = 0;
for (int i = 0; i < M; i++)
{
int u = G[i].u, v = G[i].v, c = G[i].cost;
if (find(u) == find(v)) continue;
ans += (sz[find(u)] * sz[find(v)] - 1) * (c + 1);
unite(u, v);
}
printf("%d\n", ans);
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d", &N);
M = N - 1;
for (int i = 0; i < M; i++)
{
scanf("%d%d%d", &G[i].u, &G[i].v, &G[i].cost);
}
solve();
}
return 0;
}
n ( n ≤ 500 ) n(n \le 500) n(n≤500) 个点, m ( m ≤ 1 0 4 ) m(m \le 10^4) m(m≤104) 条边,边长不超过 1 0 9 10^9 109,问次小生成树的值是多少.
#include
#include
#include
using namespace std;
const int maxn = 510, maxm = 10010;
typedef long long ll;
int h[maxn], ne[maxn * 2], e[maxn * 2], w[maxn * 2], idx;
int N, M, p[maxn], d1[maxn][maxn], d2[maxn][maxn];
ll sum;
struct P {
int u, v, c;
bool f;
bool operator<(const P& rhp)const {
return c < rhp.c;
}
}G[maxm];
void init(int N) {
for (int i = 1; i <= N; i++) p[i] = i;
}
int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
void unite(int a, int b) {
if (find(a) == find(b)) return;
p[find(a)] = find(b);
}
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void mst() {
init(N);
sort(G, G + M);
for (int i = 0; i < M; i++) {
int u = G[i].u, v = G[i].v, c = G[i].c;
if (find(u) == find(v)) continue;
unite(u, v);
G[i].f = 1;
sum += c;
add(u, v, c), add(v, u, c);
}
}
void dfs(int u, int fa, int max1, int max2, int d1[], int d2[]) {
//一旦出现最大值,就把最大值变成次大值。
d1[u] = max1, d2[u] = max2;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v != fa) {
int t1 = max1, t2 = max2;
if (w[i] > max1) t2 = max1, t1 = w[i];
else if (w[i] < max1 && w[i] > max2) t2 = w[i];
dfs(v, u, t1, t2, d1, d2);
}
}
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &N, &M);
for (int i = 0; i < M; i++) {
scanf("%d%d%d", &G[i].u, &G[i].v, &G[i].c);
}
mst();
for (int i = 1; i <= N; i++) dfs(i, -1, 0, 0, d1[i], d2[i]);
ll ans = 1e18;
for (int i = 0; i < M; i++) {
int u = G[i].u, v = G[i].v, c = G[i].c;
//printf("### %d %d %d %d %d %d\n", u, v, c, G[i].f, d1[u][v], d2[u][v]);
if (!G[i].f) {
if(c > d1[u][v]) ans = min(ans, sum + c - d1[u][v]);
else if (c == d1[u][v] && c > d2[u][v]) ans = min(ans, sum + c - d2[u][v]);
}
}
printf("%lld\n", ans);
return 0;
}
int N, M, color[maxn];
int h[maxn], e[maxm], ne[maxm], idx;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool dfs(int u, int c) {
color[u] = c;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!color[v] && !dfs(v, -c)) return false;
else if (color[v] == c) return false;
}
return true;
}
void check() {
for (int i = 1; i <= N; i++) {
if (!color[i]) {
if (!dfs(i, 1)) {
printf("No\n");
return;
}
}
}
printf("Yes\n");
}
#include
#include
using namespace std;
const int maxn = 20010, maxm = 200010;
int h[maxn], e[maxm], ne[maxm], w[maxm], idx;
int N, M, color[maxn];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
bool dfs(int u, int c, int x) {
color[u] = c;
for (int i = h[u]; i != -1; i = ne[i]) {
if (w[i] <= x) continue;
int v = e[i];
//小心这里的!dfs(v, -c, x), 千万别写错!
if (!color[v] && !dfs(v, -c, x)) return false;
else if (color[v] == c) return false;
}
return true;
}
bool C(int x) {
memset(color, 0, sizeof color);
for (int i = 1; i <= N; i++) {
if (!color[i]) {
if (!dfs(i, 1, x)) return false;
}
}
return true;
}
int main() {
scanf("%d%d", &N, &M);
memset(h, -1, sizeof h);
for (int i = 0; i < M; i++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
int lb = -1, ub = 1e9 + 1;
while (ub - lb > 1) {
int mid = (lb + ub) / 2;
if (C(mid)) ub = mid;
else lb = mid;
}
printf("%d\n", ub);
return 0;
}
贪心+二分、并查集
这个还是依附于二分图。我们从大到小把边的权值进行排序。然后,由于我们尽可能让矛盾大的两个罪犯关到不同的监狱里,因此,把边的权值从大到小遍历,并分别加到二分图的两个不同的位置。一旦发现冲突的时候,就说明可以输出答案了。
可以画一个只有 1 , 2 , 3 1,2,3 1,2,3 的例子, 1 1 1 和 2 2 2 冲突, 2 2 2 和 3 3 3 冲突, 1 1 1 和 3 3 3 冲突,那么我们只能保证其中的两个不会发生冲突,那么第三个冲突发生的时候,我们会发现他们两个的虚点已经在同一个并查集中了.
这样,我们利用虚点就可以判断了。注意处理边界!如N == 1的时候。
什么是虚点?虚点怎么用?AcWing 257题解
#include
#include
using namespace std;
const int maxm = 100010;
int p[40010];
void init(int N) {
for (int i = 1; i <= N; i++) p[i] = i;
}
int find(int x) {
if (x == p[x]) return x;
return p[x] = find(p[x]);
}
void unite(int x, int y) {
if (find(x) == find(y)) return;
p[find(x)] = find(y);
}
struct e {
int u, v, w;
bool operator < (const e& rhe)const {
return w > rhe.w;
}
}G[maxm];
int main() {
int N, M;
scanf("%d%d", &N, &M);
for (int i = 0; i < M; i++) {
scanf("%d%d%d", &G[i].u, &G[i].v, &G[i].w);
}
sort(G, G + M);
init(2 * N);
for (int i = 0; i < M; i++) {
int px = find(G[i].u), py = find(G[i].v);
if (px == py) {
printf("%d\n", G[i].w);
return 0;
}
p[px] = find(G[i].v + N);
p[py] = find(G[i].u + N);
}
printf("%d\n", 0);
return 0;
}
必须要指出,匈牙利算法建立的都是单向边!
给定一个二分图,其中左半部包含 n 1 n1 n1 个点(编号 1 ∼ n 1 1∼n_1 1∼n1),右半部包含 n 2 n_2 n2 个点(编号 1 ∼ n 2 1∼n_2 1∼n2),二分图共包含 m m m 条边。数据保证任意一条边的两个端点都不可能在同一部分中。请你求出二分图的最大匹配数。
一般运行时间都远小于 O ( m n ) O(mn) O(mn)。
这个算法就是求二分图的最大匹配数,即匹配的边数。尽管 d f s dfs dfs 是从顶点出发,但是最后的 a n s ans ans 就是最大匹配的边数。
有一个需要强调一下,尽管二分图是无向图问题,但是我们处理的时候都是将一条无向边转化为一条有向边。
一定要小心,这个算法的节点编号是从1开始的,如果从0开始,就得把match数组初始化为-1,在改动一下相关部分
int h[maxn], e[maxm], ne[maxm], idx;
int N1, N2, M, match[maxn];
bool vis[maxn];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool find(int u) {
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!vis[v]) {
vis[v] = true;
//小心这个地方是find(match[v]), 极易错!
if (match[v] == 0 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main() {
scanf("%d%d%d", &N1, &N2, &M);
memset(h, -1, sizeof h);
while(M--) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
int ans = 0;
for (int i = 1; i <= N1; i++) {
memset(vis, false, sizeof vis);
if (find(i)) ans++;
}
printf("%d\n", ans);
return 0;
}
给定一个 N 行 N 列的棋盘,已知某些格子禁止放置。求最多能往棋盘上放多少块的长度为 2、宽度为 1 的骨牌,骨牌的边界与格线重合(骨牌占用两个格子),并且任意两张骨牌都不重叠。
i + j i + j i+j 是奇数时与 i + j i + j i+j 是偶数时的格子是两两相邻的。而奇偶性相同时是不相邻的。这就是一个二分图。
#include
#include
#include
using namespace std;
const int maxn = 110;
typedef pair<int, int> P;
P match[maxn][maxn];
int N, M;
bool vis[maxn][maxn], g[maxn][maxn];
int dx[] = { 1, -1, 0, 0 }, dy[] = { 0, 0, 1, -1 };
bool find(int x, int y) {
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 1 || nx > N || ny < 1 || ny > N) continue;
if (vis[nx][ny] || g[nx][ny]) continue;
vis[nx][ny] = true;
P t = match[nx][ny];
if (!t.first || find(t.first, t.second)) {
//小心!make_pair(x, y)别写错!
match[nx][ny] = make_pair(x, y);
return true;
}
}
return false;
}
int main() {
scanf("%d%d", &N, &M);
while (M--) {
int a, b;
scanf("%d%d", &a, &b);
g[a][b] = true;
}
int ans = 0;
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
if ((i + j) % 2 && !g[i][j]) {
memset(vis, 0, sizeof vis);
if (find(i, j)) ans++;
}
}
}
printf("%d\n", ans);
return 0;
}
有两台机器 A , B A,B A,B 以及 K K K 个任务。机器 A A A 有 N 种不同的模式(模式 0 ∼ N − 1 0∼N−1 0∼N−1),机器 B B B 有 M M M 种不同的模式(模式 0 ∼ M − 1 0∼M−1 0∼M−1)。两台机器最开始都处于模式 0。每个任务既可以在 A A A 上执行,也可以在 B B B 上执行。对于每个任务 i i i,给定两个整数 a [ i ] a[i] a[i] 和 b [ i ] b[i] b[i],表示如果该任务在 A A A 上执行,需要设置模式为 a [ i ] a[i] a[i],如果在 B B B 上执行,需要模式为 b [ i ] b[i] b[i]。任务可以以任意顺序被执行,但每台机器转换一次模式就要重启一次。求怎样分配任务并合理安排顺序,能使机器重启次数最少。
仔细分析,这个类似最小点覆盖的问题。但是有两点需要注意:
#include
using namespace std;
const int maxn = 110, maxm = 2010;
int h[maxn], e[maxm], ne[maxm], idx;
int N1, N2, M, match[maxn];
bool vis[maxn];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool find(int u) {
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!vis[v]) {
vis[v] = true;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main() {
while (scanf("%d", &N1) && N1) {
memset(h, -1, sizeof h);
memset(match, -1, sizeof match);
idx = 0;
scanf("%d%d", &N2, &M);
for (int i = 0; i < M; i++) {
int id, a, b;
scanf("%d%d%d", &id, &a, &b);
//0的时候不用转换状态。
if (a == 0 || b == 0) continue;
add(a, b);
}
int ans = 0;
for (int i = 1; i < N1; i++) {
memset(vis, 0, sizeof vis);
if (find(i)) ans++;
}
printf("%d\n", ans);
}
return 0;
}
给定一个 N×M 的棋盘,有一些格子禁止放棋子。问棋盘上最多能放多少个不能互相攻击的骑士(国际象棋的“骑士”,类似于中国象棋的“马”,按照“日”字攻击,但没有中国象棋“别马腿”的规则)。
由此可以看出,骑士每跳一次,格子的奇偶性就会改变。这个问题就等价于,能选出多少个格子,使得两两之间没有边。最终答案就是:N * M - k - 最大匹配数量
一般的图中求最大独立集是一个 NP-Hard 问题,只有二分图才可以用匈牙利算法。
#include
#include
#include
using namespace std;
const int maxn = 110;
typedef pair<int, int> P;
P match[maxn][maxn];
bool g[maxn][maxn], vis[maxn][maxn];
int N, M, K;
int dx[] = { -1, -2, -2, -1, 1, 2, 2, 1 }, dy[] = { -2, -1, 1, 2, 2, 1, -1, -2 };
bool find(int x, int y) {
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 1 || nx > N || ny < 1 || ny > M) continue;
if (vis[nx][ny] || g[nx][ny]) continue;
vis[nx][ny] = true;
P t = match[nx][ny];
if (t.first == 0 || find(t.first, t.second)) {
match[nx][ny] = { x, y };
return true;
}
}
return false;
}
int main() {
scanf("%d%d%d", &N, &M, &K);
for(int i = 0; i < K; i++) {
int x, y;
scanf("%d%d", &x, &y);
g[x][y] = true;
}
int res = 0;
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
//这个地方写成 (i + j) % 2 == 1 也是可以过的。
if ((i + j) % 2 || g[i][j]) continue;
memset(vis, 0, sizeof vis);
if (find(i, j)) res++;
}
}
printf("%d\n", N * M - K - res);
return 0;
}
#include
using namespace std;
const int maxn = 210;
bool d[maxn][maxn], vis[maxn];
int N, M, match[maxn];
void Floyd_Warshall() {
for (int k = 1; k <= N; k++) {
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) d[i][j] |= d[i][k] & d[k][j];
}
}
}
bool find(int u) {
for (int i = 1; i <= N; i++) {
if (d[u][i] && !vis[i]) {
vis[i] = true;
//一开始又双叒叕把 find(match[i]) 写错了!看清楚!
if (!match[i] || find(match[i])) {
match[i] = u;
return true;
}
}
}
return false;
}
int main() {
scanf("%d%d", &N, &M);
for (int i = 0; i < M; i++) {
int a, b;
scanf("%d%d", &a, &b);
d[a][b] = true;
}
Floyd_Warshall();
int res = 0;
for (int i = 1; i <= N; i++) {
memset(vis, 0, sizeof vis);
if (find(i)) res++;
}
printf("%d\n", N - res);
return 0;
}
**解释:如果是求最大值,那么我们有一系列 x n ≤ x 0 + ∑ a i c i x_n \le x_0 + \sum\limits a_ic_i xn≤x0+∑aici 不等式,那么要满足所有不等式的话,就要满足最小上界,因此是求最小值,即求最短路. **
如何求最大值或者最小值(最大的可行解或是最小的可行解):
结论:如果是求最小值,则应该求最长路;如果是求最大值,则应该求最短路。
问题:如何转化 x i ≤ C x_i \le C xi≤C,其中 c c c 是一个常数,这类的不等式
方法:建立一个超级源点, 0,然后建立 0 → i 0 \rightarrow i 0→i, 长度是 c c c 的边即可。
以求 x i x_i xi 的最大值为例:求所有从 x i x_i xi 出发,构成的不等式链 x i ≤ x j + c 1 ≤ x k + c 2 + c 1 ≤ . . . ≤ c 1 + c 2 + . . . + c k x_i \le x_j+ c_1 \le x_k+c_2+c_1 \le... \le c_1+c_2+... + c_k xi≤xj+c1≤xk+c2+c1≤...≤c1+c2+...+ck
所计算出的上界,最终 x i x_i xi 的最大值等于所有上界的最小值。
还是要强调一下,求负环的话还是要优先选择朴素的方法,用队列与cnt数组来实现。不过TLE的话还是改成栈吧。
总之:最小值问题,转化为一系列大于等于不等式,并求最长路;最大值问题,转化为一系列小于等于不等式,并求最短路。
对 n ( n ≤ 1000 ) n(n \le 1000) n(n≤1000) 个点摆放位置(这些点要按照编号在数轴上排成一队),但是点与点之间的距离需要满足一定的条件: M L ( 1 ≤ M L ≤ 10 , 000 ) ML(1 \le ML \le 10,000) ML(1≤ML≤10,000) 描述了某两个点之间的距离不能超过多少; M D ( 1 ≤ M D ≤ 10 , 000 ) MD(1 \le MD \le 10,000) MD(1≤MD≤10,000) 描述了某两个点之间的距离不能少于多少。计算 1 1 1 号点和 n n n 号点的最大距离.输出一个整数,如果不存在满足要求的方案,输出-1;如果 1 号奶牛和 N 号奶牛间的距离可以任意大,输出-2;
x 0 ≤ x i − 1 x j ≤ x i + c ( i < j ) x i ≤ x j − c ( i < j ) x i − 1 ≤ x i x_0 \le x_i - 1 \\ x_j \le x_i + c \quad (i < j) \\ x_i \le x_j - c \quad (i < j) \\ x_{i-1} \le x_i x0≤xi−1xj≤xi+c(i<j)xi≤xj−c(i<j)xi−1≤xi
#include
using namespace std;
typedef long long ll;
const int maxn = 1010, maxm = 21010;
const ll INF = 1e18;
int h[maxn], e[maxm], ne[maxm], w[maxm], idx;
int N, M1, M2, cnt[maxn];
ll d[maxn];
bool vis[maxn];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
ll spfa() {
queue<int> que;
fill(d, d + maxn, INF);
d[1] = 0;
for (int i = 1; i <= N; i++)
{
vis[i] = true;
que.push(i);
}
while (que.size())
{
int u = que.front(); que.pop();
vis[u] = false;
for (int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if (d[v] > d[u] + w[i]) {
d[v] = d[u] + w[i];
cnt[v] = cnt[u] + 1;
if (cnt[v] >= N) return -1;
if (!vis[v]) {
vis[v] = true;
que.push(v);
}
}
}
}
if (d[N] >= INF / 2) return -2;
return d[N];
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d%d", &N, &M1, &M2);
for (int i = 0; i < M1; i++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if (a > b) swap(a, b);
add(a, b, c);
}
for (int i = 0; i < M2; i++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if (a > b) swap(a, b);
add(b, a, -c);
}
for (int i = 2; i <= N; i++) {
add(i, i - 1, 0);
}
ll ans = spfa();
printf("%lld\n", ans);
return 0;
}
#include
#include
#include
#include
using namespace std;
typedef long long ll;
//这个maxm必须开到300010,因为x=1时会加双向边,而且每一个节点与0都要连一条边。
const int maxn = 100010, maxm = 300010;
const ll INF = 1e16;
int h[maxn], e[maxm], ne[maxm], w[maxm], idx;
int N, M, cnt[maxn];
bool vis[maxn];
ll d[maxn];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
bool spfa() {
stack<int> s;
fill(d, d + maxn, -INF);
//当时spfa求负环把所有点都加到队列里面的一个重要原因是图可能是不连通的。
//但是这个图从0节点一定可以遍历完所有节点,因此只把0节点加入栈中就可以。
d[0] = 0, s.push(0), vis[0] = true;
while (s.size()) {
int u = s.top(); s.pop();
vis[u] = false;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (d[v] < d[u] + w[i]) {
d[v] = d[u] + w[i];
cnt[v] = cnt[u] + 1;
//注意,因为加上0号节点了,所以是N + 1个点。
if (cnt[v] >= N + 1) return true;
if (!vis[v]) {
s.push(v);
vis[v] = true;
}
}
}
}
return false;
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; i++) add(0, i, 1);
for (int i = 0; i < M; i++) {
int x, a, b;
scanf("%d%d%d", &x, &a, &b);
if (x == 1) add(a, b, 0), add(b, a, 0);
else if (x == 2) add(a, b, 1);
else if (x == 3) add(b, a, 0);
else if (x == 4) add(b, a, 1);
else if (x == 5) add(a, b, 0);
}
if (spfa()) printf("-1\n");
else {
ll ans = 0;
for (int i = 1; i <= N; i++) ans += d[i];
printf("%lld\n", ans);
}
return 0;
}
#include
#include
#include
#include
using namespace std;
const int maxn = 40010, maxm = maxn * 2;
int h[maxn], ne[maxm], e[maxm], idx;
int N, M, root;
int depth[maxn], fa[maxn][16];
void bfs() {
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[root] = 1;
queue<int> que;
que.push(root);
while (que.size()) {
int u = que.front(); que.pop();
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (depth[v] > depth[u] + 1) {
depth[v] = depth[u] + 1;
que.push(v);
fa[v][0] = u;
for (int k = 1; k <= 15; k++) fa[v][k] = fa[fa[v][k - 1]][k - 1];
}
}
}
}
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int lca(int a, int b) {
if (depth[a] < depth[b]) swap(a, b);
//二次方拼凑一定要倒着拼凑。
for (int k = 15; k >= 0; k--) {
if (depth[fa[a][k]] >= depth[b]) a = fa[a][k];
}
if (a == b) return a;
for (int k = 15; k >= 0; k--) {
if (fa[a][k] != fa[b][k]) {
a = fa[a][k];
b = fa[b][k];
}
}
//返回a的节点,即a的公共祖先。
return fa[a][0];
}
int main() {
scanf("%d", &N);
memset(h, -1, sizeof h);
for (int i = 0; i < N; i++) {
int a, b;
scanf("%d%d", &a, &b);
if (b == -1) root = a;
else add(a, b), add(b, a);
}
bfs();
scanf("%d", &M);
while (M--) {
int a, b;
scanf("%d%d", &a, &b);
int p = lca(a, b);
if (p == a) printf("1\n");
else if (p == b) printf("2\n");
else printf("0\n");
}
return 0;
}
#include
#include
#include
#include
using namespace std;
typedef pair<int, int> P;
const int maxn = 10010, maxm = maxn * 2;
int h[maxn], ne[maxm], e[maxm], w[maxm], idx;
int st[maxn], N, M, p[maxn], ans[maxm], d[maxn];
vector<P> query[maxn];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void dfs(int u, int fa) {
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v == fa) continue;
d[v] = d[u] + w[i];
dfs(v, u);
}
}
void init(int N){
for(int i = 1; i <= N; i++) p[i] = i;
}
int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
void tarjan(int u) {
st[u] = 1;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!st[v]) {
tarjan(v);
//只有状态为 2 的点往上走,走到第一个状态为 1 的点才可以算作并查集中的 parent。因此只有这个点完全变成状态为2的点的时候,才可以让 p[v] 变为 u.
p[v] = u;
}
}
for (auto p : query[u]) {
int v = p.first, id = p.second;
if (st[v] == 2) {
int anc = find(v);
ans[id] = d[u] + d[v] - 2 * d[anc];
}
}
st[u] = 2;
}
int main() {
scanf("%d%d", &N, &M);
memset(h, -1, sizeof h);
for (int i = 1; i < N; i++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
for (int i = 0; i < M; i++) {
int a, b;
scanf("%d%d", &a, &b);
if (a != b) {
query[a].push_back({ b, i });
query[b].push_back({ a, i });
}
}
init(N);
dfs(1, -1);
tarjan(1);
for (int i = 0; i < M; i++) printf("%d\n", ans[i]);
return 0;
}
给定一张 N N N 个点 M M M 条边的无向图,求无向图的严格次小生成树。设最小生成树的边权之和为 s u m sum sum,严格次小生成树就是指边权之和大于 s u m sum sum 的生成树中最小的一个。 N ≤ 1 0 5 , M ≤ 3 × 1 0 5 N≤10^5,M≤3×10^5 N≤105,M≤3×105
f a ( i , j ) fa(i, j) fa(i,j):从 i i i 开始,往上跳 2 j 2^j 2j 步是哪个节点。
d 1 ( i , j ) d_1(i, j) d1(i,j):从 i i i 开始,往上跳 2 j 2^j 2j 步,这些 2 j 2^j 2j 边中边权最大值。
d 2 ( i , j ) d_2(i, j) d2(i,j):从 i i i 开始,往上跳 2 j 2^j 2j 步,这些 2 j 2^j 2j 边中边权次大值。
思路:
#include
using namespace std;
typedef long long ll;
const int maxn = 100010, maxm = 300010, INF = 0x3f3f3f3f;
int h[maxn], e[maxm], ne[maxm], w[maxm], idx;
int p[maxn], N, M;
int d1[maxn][17], d2[maxn][17], fa[maxn][17], depth[maxn];
ll sum;
struct edge {
int u, v, w;
bool used;
bool operator<(const edge& rhe)const
{
return w < rhe.w;
}
}G[maxm];
void init(int N)
{
for (int i = 1; i <= N; i++) p[i] = i;
}
int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
void unite(int a, int b) {
if (find(a) == find(b)) return;
p[find(a)] = find(b);
}
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void kruskal() {
init(N);
sort(G, G + M);
for (int i = 0; i < M; i++) {
int u = G[i].u, v = G[i].v, w = G[i].w;
if (find(u) != find(v)) {
unite(u, v);
G[i].used = true;
sum += w;
}
}
}
//建出最小生成树构成的数。
void build() {
memset(h, -1, sizeof h);
for (int i = 0; i < M; i++) {
if (G[i].used) {
add(G[i].u, G[i].v, G[i].w);
add(G[i].v, G[i].u, G[i].w);
}
}
}
queue<int> que;
//计算出depth, d1, d2, fa.
void bfs() {
memset(depth, 0x3f3f3f3f, sizeof depth);
depth[0] = 0, depth[1] = 1;
que.push(1);
while (que.size()) {
int u = que.front();
que.pop();
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (depth[v] > depth[u] + 1) {
depth[v] = depth[u] + 1;
que.push(v);
fa[v][0] = u;
d1[v][0] = w[i], d2[v][0] = -INF;
for (int k = 1; k <= 16; k++) {
int anc = fa[v][k - 1];
fa[v][k] = fa[anc][k - 1];
d1[v][k] = d2[v][k] = -INF;
int dis[4] = { d1[v][k - 1], d2[v][k - 1], d1[anc][k - 1], d2[anc][k - 1] };
for (int j = 0; j < 4; j++) {
if (dis[j] > d1[v][k])
d2[v][k] = d1[v][k], d1[v][k] = dis[j];
else if (dis[j] != d1[v][k] && dis[j] > d2[v][k])
d2[v][k] = dis[j];
}
}
}
}
}
}
int lca(int a, int b, int w) {
static int dis[maxn * 2];
int cnt = 0;
if (depth[a] < depth[b]) swap(a, b);
for (int k = 16; k >= 0; k--) {
if (depth[fa[a][k]] >= depth[b]) {
dis[cnt++] = d1[a][k];
dis[cnt++] = d2[a][k];
a = fa[a][k];
}
}
if (a != b) {
for (int k = 16; k >= 0; k--) {
if (fa[a][k] != fa[b][k]) {
dis[cnt++] = d1[a][k];
dis[cnt++] = d2[a][k];
dis[cnt++] = d1[b][k];
dis[cnt++] = d2[b][k];
a = fa[a][k], b = fa[b][k];
}
dis[cnt++] = d1[a][0];
dis[cnt++] = d1[b][0];
}
}
int dist1 = -INF, dist2 = -INF;
for (int i = 0; i < cnt; i++) {
if (dis[i] > dist1) dist2 = dist1, dist1 = dis[i];
else if (dis[i] != dist1 && dis[i] > dist2) dist2 = dis[i];
}
if (w > dist1) return w - dist1;
if (w > dist2) return w - dist2;
return INF;
}
int main() {
scanf("%d%d", &N, &M);
for (int i = 0; i < M; i++) {
scanf("%d%d%d", &G[i].u, &G[i].v, &G[i].w);
}
kruskal();
build();
bfs();
ll ans = 1e18;
for (int i = 0; i < M; i++) {
if (!G[i].used) {
int u = G[i].u, v = G[i].v, w = G[i].w;
ans = min(ans, sum + lca(u, v, w));
}
}
printf("%lld\n", ans);
return 0;
}
题意:Dark 有 N–1 条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径。另外,Dark 还有 M 条附加边。你的任务是把 Dark 斩为不连通的两部分。第一步需要切断一条主要边,第二步需要切断一条附加边.
我们定义题目所说的主要边为树边,附加边为非树边。任意一条非树边都可以和树边构成一个环。
假如一条树边不在图中的任何环上,那么把这条树边切断图就会不连通;若一条树边在一个环上,在切掉形成那个环的非树边就能变成不连通的图。但是在两个环上的话,就不可能切成不连通的图。
因此,我们枚举每条非树边,把构成的环上的每条树边的值 + 1 +1 +1。最后枚举每条树边,若 c = 0 c = 0 c=0,则 a n s + = M ans += M ans+=M;若 c = 1 c = 1 c=1,则 a n s + = 1 ans += 1 ans+=1;若 c > 1 c > 1 c>1,则 a n s + = 0 ans += 0 ans+=0.
问题就变成了:怎样快速给树上的一些边都增加一个值,怎样快速求出树上的边的值。
树上差分:设 x , y x, y x,y 是非树边的两个端点, x , y x, y x,y 的最近公共祖先是 p p p。则 d [ x ] + = c , d [ y ] + = c , d [ p ] − = 2 c d[x] += c, d[y] += c, d[p] -= 2c d[x]+=c,d[y]+=c,d[p]−=2c。树上差分的原理和数组差分差不多,都是为了方便修改某一个区间的值。其实沿着路径将对应的d数组的值加起来,就是这个地方对应的原本数组的值。
#include
using namespace std;
const int maxn = 100010, maxm = 2 * maxn;
int h[maxn], ne[maxm], e[maxm], idx;
int N, M, ans;
int d[maxn], depth[maxn], fa[maxn][17];
queue<int> que;
void bfs()
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[1] = 1;
que.push(1);
while (que.size()) {
int u = que.front(); que.pop();
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (depth[v] > depth[u] + 1) {
depth[v] = depth[u] + 1;
que.push(v);
fa[v][0] = u;
for (int k = 1; k <= 16; k++) fa[v][k] = fa[fa[v][k - 1]][k - 1];
}
}
}
}
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int lca(int a, int b) {
if (depth[a] < depth[b]) swap(a, b);
for (int k = 16; k >= 0; k--) {
if (depth[fa[a][k]] >= depth[b]) {
a = fa[a][k];
}
}
if (a == b) return a;
for (int k = 16; k >= 0; k--) {
if (fa[a][k] != fa[b][k]) {
a = fa[a][k];
b = fa[b][k];
}
}
return fa[a][0];
}
int dfs(int u, int father) {
int res = d[u];
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v == father) continue;
int s = dfs(v, u);
if (s == 0) ans += M;
else if (s == 1) ans++;
res += s;
}
return res;
}
int main() {
scanf("%d%d", &N, &M);
memset(h, -1, sizeof h);
for (int i = 1; i < N; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
bfs();
for (int i = 0; i < M; i++) {
int a, b;
scanf("%d%d", &a, &b);
int p = lca(a, b);
d[a]++, d[b]++, d[p] -= 2;
}
dfs(1, -1);
printf("%d\n", ans);
return 0;
}