2025.4.7-5.7 学习随记(图论)
刚好一个月呐,感觉有很多都学过了,是不是可以尝试跳过一些呢。
1.单源最短路三章
就是考 dijkstra,spfa 之类,可能最难也难不到哪里去。
经常用的 trick 有虚拟源点、建反图、分层图、01bfs 之类。
2.Floyd
用来求最短路是没有难度的。
稍稍加难的是传递闭包,跟最短路写法差不多,但是应用较广泛,最小可重路径覆盖也有用到。
有一题是和广义矩阵乘法优化结合了的,是牛站。
3.最小生成树两章
也是有很多 trick,比如建虚拟源点。
不过比较突出的是次小生成树,是在树上将每两点间的最大边、次大边求出,然后枚举、替换。
4.负环和差分约束
负环,顾名思义就是边权和为负的环,求最短路遇上这个就 T。
差分约束是将题目所给条件转化为一系列不等式,建边之后跑最长路/最短路。
根据题目中要求的解最大或最小可以判断最短路或最长路。
最大就是上界的最小,故为最短路;反之则为最长路。
考虑点 i,j 间有一条 i<-j 权为 w 的边,则跑完最长路之后,必然满足 dist[i]>=dist[j]+w。
所以最长路建边就是将条件化为上面的式子,然后建边。
一般问题会求具体解,可以虚拟出一个源点,通常的限制是每个点 dist 非负,直接连 0->i,权为 0 即可。
5.LCA
倍增是最常用的了,然后特殊的有 tarjan。
可以保证 O(n) 求 LCA,不过离线。
大致就是将点分为三部分:①已经遍历过的并且不在根到当前点的路径上的②在根到当前点的路径上的点③未遍历过的。
然后通过并查集把①合并到②,每次求当前点的询问时,如果另一个点是属于①的,直接找并查集根即可。
然后是倍增 LCA 求次小生成树,思路相似,不过把记录最大边次大边改为了倍增记录。
6.有向图的强连通分量
tarjan。
强连通分量的意思为删去任意一条边都连通的连通分量。
引入 dfn,low 的概念,表示每个点遍历到的时间戳,以及能到达的时间戳最先的点的时间戳。
当一个点 dfn 等于 low 时,则可以新增一个强连通分量。
然后用栈和 dfs 结合即可求出,同时缩点。
当图中边权非负或非正时,可用 tarjan 进行差分约束,即缩点后每个块内边权应都为 0。
7.无向图的双连通分量
双连通分量是指点双和边双。
割点同时至少属于两个点双,当点 j 的 low 大于等于点 u 的 dfn 时,则点 u 为割点,注意求点双时应弹到 j 为止,因为 u 可能属于其他点双。
求桥的时候,当点 j 的 low 大于点 u 的 dfn 时,则边 u->j 为桥。缩点时的判定同有向图强连通分量是一样的。
8.二分图
指将点分为两个集合,内部的点之间无边相连。
一般问题有最大匹配、最小点覆盖、最大独立集、最小路径覆盖。
定理:最大独立集 = 最小路径覆盖(不重) = 所有顶点数 - 最小顶点覆盖 = 所有顶点数 - 最大匹配
对于可重复的最小路径覆盖先求一遍传递闭包在做二分图即可。
9.欧拉路径和欧拉回路
回路的判断条件对于有向图是每个点出度等于入度,对于无向图是度数为偶数。
路径的判断条件对于有向图是除终点起点外每个点出度等于入度,起点出度多 1,终点入度多 1或者同回路相同;对于无向图是两个度数为奇数的点加剩下全是度数为偶数的点。
当然有特例,孤立点都是。
注意遍历具体路径是最好将遍历过了的边都删除。
10.拓扑排序
没啥好说的,值得一提的是拓扑排序用来做差分约束。
在一张 DAG 上,可以用拓扑排序做差分约束。
[USACO07NOV] Cow Relays G(Floyd + 广义矩乘)
放在 Floyd 里的题,但是思路是不大相同的。
是一道广义矩阵乘法的题。
不难想到你对经过固定边数的路径先考虑,然后按边数依次往上。
考虑一条总边数为 a+b 的路径,设中间点为 k,两边为 i,j。
则更新 i,j 间边数要求为 a+b 的最短路,我们需要枚举 k,a
然后不难发现这条路径是由许多条边组成的,隔离来看,每条边之间是相互独立的。
于是就可以考虑合并路径的顺序的说,相当于快速幂,分别弄出经过 2 的幂次条边时的 dist,最后去逼近要求边数 n 即可。
就是矩阵乘法的思想,在 Floyd 上进行了一定的修改。
#include <bits/stdc++.h>
using namespace std;
const int N = 210;
int S, E;
int n, m, k;
struct Matrix
{
int a[N][N];
Matrix operator * (const Matrix &A) const
{
Matrix B;
memset(B.a, 0x3f, sizeof B);
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
B.a[i][j] = min(B.a[i][j], a[i][k] + A.a[k][j]);
return B;
}
} A;
unordered_map<int, int> mp;
signed main()
{
cin >> k >> m >> S >> E;
if (!mp.count(S)) mp[S] = ++ n;
if (!mp.count(E)) mp[E] = ++ n;
S = mp[S], E = mp[E];
memset(A.a, 0x3f, sizeof A.a);
for (int i = 1; i <= m; i ++ )
{
int a, b, c;
cin >> c >> a >> b;
if (!mp.count(a)) mp[a] = ++ n;
if (!mp.count(b)) mp[b] = ++ n;
a = mp[a], b = mp[b];
A.a[a][b] = A.a[b][a] = min(A.a[a][b], c);
}
Matrix res;
memset(res.a, 0x3f, sizeof res.a);
for (int i = 1; i <= n; i ++ ) res.a[i][i] = 0;
while (k)
{
if (k & 1) res = res * A;
A = A * A;
k >>= 1;
}
cout << res.a[S][E] << '\n';
return 0;
}
欧拉回路
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 400010;
int n, m, t;
int h[N], e[M], ne[M], idx;
int res[M], cnt;
int in[N], out[N];
bool used[M];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
for (int &i = h[u]; ~i;)
{
int j = e[i];
if (used[i])
{
i = ne[i];
continue;
}
used[i] = 1;
if (t == 1) used[i ^ 1] = 1;
int k;
if (t == 1)
{
k = i / 2 + 1;
if (i & 1) k = -k;
}
else k = i + 1;
i = ne[i];
dfs(j);
res[ ++ cnt] = k;
}
}
signed main()
{
memset(h, -1, sizeof h);
cin >> t >> n >> m;
for (int i = 1; i <= m; i ++ )
{
int a, b;
cin >> a >> b;
add(a, b);
if (t == 1) add(b, a);
out[a] ++ , in[b] ++ ;
}
if (t == 1)
{
for (int i = 1; i <= n; i ++ )
if (in[i] + out[i] & 1)
{
puts("NO");
return 0;
}
}
else
{
for (int i = 1; i <= n; i ++ )
if (in[i] != out[i])
{
puts("NO");
return 0;
}
}
for (int i = 1; i <= n; i ++ )
if (~h[i])
{
dfs(i);
break;
}
if (cnt < m)
{
puts("NO");
return 0;
}
puts("YES");
for (int i = cnt; i; i -- ) printf("%d ", res[i]);
puts("");
return 0;
}
P1983 [NOIP 2013 普及组] 车站分级
本题比较有用的点是本来建边需要 \(1000*(500*500)\) 的,会爆,但是用虚拟点之后可以变成 \(1000*(500+500)\)。
也是一个小 trick 了吧。
#include <bits/stdc++.h>
using namespace std;
const int N = 2010, M = 1000010;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int d[N], dist[N], q[M];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
d[b] ++ ;
}
void topo()
{
int hh = 0, tt = -1;
for (int i = 1; i <= n; i ++ ) dist[i] = 1;
for (int i = 1; i <= n + m; i ++ )
if (!d[i]) q[ ++ tt] = i;
while (hh <= tt)
{
auto t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (!( -- d[j])) q[ ++ tt] = j;
if (dist[j] < dist[t] + w[i]) dist[j] = dist[t] + w[i];
}
}
}
signed main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1; i <= m; i ++ )
{
int k;
cin >> k;
memset(st, 0, sizeof st);
int ST = 0, ED = 0;
for (int j = 1; j <= k; j ++ )
{
int x;
cin >> x;
st[x] = 1;
ST = j == 1 ? x : ST;
ED = j == k ? x : ED;
}
int unr = n + i;
for (int j = ST; j <= ED; j ++ )
{
if (!st[j]) add(j, unr, 0);
else add(unr, j, 1);
}
}
topo();
int res = 0;
for (int i = 1; i <= n; i ++ ) res = max(res, dist[i]);
cout << res << '\n';
return 0;
}
P2272 [ZJOI2007] 最大半连通子图
感觉考点就是思维吧。
其他都是板子,主要就是想那个最大的一定是缩点之后的最长链。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100010, M = 2000010;
int n, m, mod;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], time_stamp;
int stk[N], top;
int scc_cnt, siz[N], id[N];
bool in_stk[N];
int f[N], g[N];
vector<int> gph[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++ time_stamp;
stk[ ++ top] = u, in_stk[u] = true;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u])
{
++ scc_cnt;
int y;
do {
y = stk[top -- ];
in_stk[y] = false;
id[y] = scc_cnt;
siz[scc_cnt] ++ ;
} while (y != u);
}
}
signed main()
{
memset(h, -1, sizeof h);
cin >> n >> m >> mod;
for (int i = 1; i <= m; i ++ )
{
int a, b;
cin >> a >> b;
add(a, b);
}
for (int i = 1; i <= n; i ++ )
if (!dfn[i]) tarjan(i);
unordered_map<int, bool> mp;
for (int i = 1; i <= n; i ++ )
for (int j = h[i]; ~j; j = ne[j])
{
int k = e[j];
if (mp.count(id[i] * 1000000 + id[k])) continue;
if (id[i] != id[k]) gph[id[i]].push_back(id[k]);
mp[id[i] * 1000000 + id[k]] = true;
}
for (int i = scc_cnt; i; i -- )
{
if (!f[i])
{
f[i] = siz[i];
g[i] = 1;
}
for (int j : gph[i])
{
if (f[j] < f[i] + siz[j])
{
f[j] = f[i] + siz[j];
g[j] = g[i];
}
else if (f[j] == f[i] + siz[j]) (g[j] += g[i]) %= mod;
}
}
int mx = 0, sum = 0;
for (int i = 1; i <= scc_cnt; i ++ )
if (f[i] > mx)
{
mx = f[i];
sum = g[i];
}
else if (f[i] == mx) (sum += g[i]) %= mod;
cout << mx << '\n' << sum << '\n';
return 0;
}
银河(tarjan差分约束)
和差分约束相比的话,快了一些,不易被卡。
但是使用条件多,内存,而且必须要求全正权或全负权。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100010, M = 600010;
int n, m;
int h[N], e[M], ne[M], w[M], idx;
int dfn[N], low[N], time_stamp;
int stk[N], top;
int scc_cnt, siz[N], id[N];
bool in_stk[N];
int dist[N];
vector<pair<int, int> > gph[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++ time_stamp;
stk[ ++ top] = u, in_stk[u] = true;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u])
{
++ scc_cnt;
int y;
do {
y = stk[top -- ];
in_stk[y] = false;
id[y] = scc_cnt;
siz[scc_cnt] ++ ;
} while (y != u);
}
}
signed main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) add(0, i, 1);
for (int i = 1; i <= m; i ++ )
{
int t, a, b;
cin >> t >> a >> b;
if (t == 1) add(a, b, 0), add(b, a, 0);
else if (t == 2) add(a, b, 1);
else if (t == 3) add(b, a, 0);
else if (t == 4) add(b, a, 1);
else add(a, b, 0);
}
tarjan(0);
bool flg = true;
for (int i = 0; i <= n; i ++ )
{
for (int j = h[i]; ~j; j = ne[j])
{
int k = e[j];
if (id[i] != id[k]) gph[id[i]].push_back({id[k], w[j]});
else if (w[j] > 0) flg = false;
}
if (!flg) break;
}
if (!flg) puts("-1"), exit(0);
for (int i = scc_cnt; i; i -- )
for (auto [j, w] : gph[i])
dist[j] = max(dist[j], dist[i] + w);
int res = 0;
for (int i = 1; i <= scc_cnt; i ++ )
res += siz[i] * dist[i];
cout << res << '\n';
return 0;
}
昂贵的聘礼(加边)
/*
在加边的方式上,考阅读理解。
加边就是从每个物品向它能兑换的物品连边。
同时建一个源点0,跑最短路,答案为dist[1]。
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int m, n;
int g[N][N];
int p[N], l[N];
int dis[N], st[N];
int dijkstra(int low, int up)
{
memset(st, 0, sizeof st);
memset(dis, 0x3f, sizeof dis);
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
q.push({0, 0});
dis[0] = 0;
while (q.size())
{
int t = q.top().second; q.pop();
if (st[t]) continue;
st[t] = 1;
for (int i = 1; i <= n; i ++ )
if (low <= l[i] && l[i] <= up && dis[i] > dis[t] + g[t][i])
{
dis[i] = dis[t] + g[t][i];
q.push({dis[i], i});
}
}
return dis[1];
}
signed main()
{
memset(g, 0x3f, sizeof g);
cin >> m >> n;
for (int i = 1; i <= n; i ++ )
{
int k;
cin >> p[i] >> l[i] >> k;
g[0][i] = min(g[0][i], p[i]);
for (int j = 1; j <= k; j ++ )
{
int u, w;
cin >> u >> w;
g[u][i] = min(g[u][i], w);
}
}
int res = 0x3f3f3f3f;
for (int i = l[1] - m; i <= l[1]; i ++ ) res = min(res, dijkstra(i, i + m));
cout << res << '\n';
return 0;
}
单词环(负环+建图)
#include <bits/stdc++.h>
using namespace std;
const int N = 710, M = 100010;
const double eps = 1e-4;
int n;
double dis[N];
int h[N], e[M], w[M], ne[M], idx;
int cnt[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool check(double mid)
{
memset(dis, 0, sizeof dis);
memset(st, 0, sizeof st);
memset(cnt, 0, sizeof cnt);
queue<int> q;
for (int i = 0; i < N; i ++ )
{
q.push(i);
st[i] = 1;
}
int count = 0;
while (q.size())
{
int t = q.front(); q.pop();
st[t] = 0;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dis[j] < dis[t] + (double)w[i] - mid)
{
dis[j] = dis[t] + (double)w[i] - mid;
cnt[j] = cnt[t] + 1;
count ++ ;
if (count > 10 * N) return true;
if (cnt[j] >= N) return true;
if (!st[j])
{
q.push(j);
st[j] = 1;
}
}
}
}
return false;
}
signed main()
{
while (cin >> n, n)
{
memset(h, -1, sizeof h);
idx = 0;
for (int i = 1; i <= n; i ++ )
{
string str;
cin >> str;
int len = str.size();
if (len >= 2)
{
int a = (str[0] - 'a') * 26 + str[1] - 'a';
int b = (str[len - 2] - 'a') * 26 + str[len - 1] - 'a';
add(a, b, len);
}
}
if (!check(0))
{
puts("No solution");
continue;
}
double l = 0, r = 1010, res = 0;
while (r - l >= eps)
{
double mid = (l + r) / 2;
if (check(mid)) res = mid, l = mid;
else r = mid;
}
printf("%.2lf\n", res);
}
return 0;
}
P2850 [USACO06DEC] Wormholes G(负环)
#include <bits/stdc++.h>
using namespace std;
const int N = 510, M = 5210;
int n, m1, m2;
int h[N], e[M], w[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool spfa()
{
memset(dist, 0, sizeof dist);
memset(cnt, 0, sizeof cnt);
memset(st, 0, sizeof st);
queue<int> q;
for (int i = 1; i <= n; i ++ )
{
q.push(i);
st[i] = true;
}
while (q.size())
{
int t = q.front(); q.pop();
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
signed main()
{
int T;
cin >> T;
while (T -- )
{
memset(h, -1, sizeof h);
idx = 0;
cin >> n >> m1 >> m2;
for (int i = 1; i <= m1; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
for (int i = 1; i <= m2; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, -c);
}
if (spfa()) puts("YES");
else puts("NO");
}
return 0;
}
排序(传递闭包)
传递闭包的模板题。
直接传递闭包即可,每次传递完暴力判断。
无解即为存在 i 使得 d[i][i]=1,解唯一确定当且仅当任意 i,j 在 d[i][j] 和 d[j][i] 中有且只有一个为 1。
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int g[N][N];
bool st[N];
int check()
{
for (int i = 1; i <= n; i ++ )
if (g[i][i]) return 2;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j < i; j ++ )
if (!g[i][j] && !g[j][i])
return 0;
return 1;
}
void print()
{
for (int t = 1; t <= n; t ++ )
{
for (int i = 1; i <= n; i ++ )
{
if (st[i]) continue;
bool flg = 1;
for (int j = 1; j <= n; j ++ )
if (g[j][i] && !st[j])
{
flg = 0;
break;
}
if (flg)
{
st[i] = 1;
cout << (char)(i + 'A' - 1);
break;
}
}
}
}
signed main()
{
while (cin >> n >> m, n || m)
{
memset(g, 0, sizeof g);
memset(st, 0, sizeof st);
bool flg = 0;
for (int i = 1; i <= m; i ++ )
{
char a, b;
cin >> a >> b >> b;
if (!flg)
{
int A = a - 'A' + 1, B = b - 'A' + 1;
g[A][B] = 1;
for (int x = 1; x <= n; x ++ )
{
if (g[x][A]) g[x][B] = 1;
if (g[B][x]) g[A][x] = 1;
for (int y = 1; y <= n; y ++ )
if (g[x][A] && g[B][y])
g[x][y] = 1;
}
}
if (!flg)
{
int flag = check();
if (flag == 1)
{
printf("Sorted sequence determined after %d relations: ", i);
print();
puts(".");
flg = 1;
}
else if (flag == 2) flg = 1, printf("Inconsistency found after %d relations.\n", i);
}
}
if (!flg) puts("Sorted sequence cannot be determined.");
}
return 0;
}
/*
6 6
A<B
B<C
C<D
B<E
E<F
F<C
0 0
*/
严格次小生成树
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 600010, K = 20, inf = 0x3f3f3f3f;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
struct Edge
{
int a, b, w;
bool st;
bool operator < (const Edge & W) const
{
return w < W.w;
}
} edge[N];
int p[N];
int q[N], dist[N], depth[N];
int fa[N][K], d1[N][K], d2[N][K];
int d[N * 2];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void bfs()
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[1] = 1;
int hh = 0, tt = 0;
q[0] = 1;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q[ ++ tt] = j;
fa[j][0] = t, d1[j][0] = w[i], d2[j][0] = -inf;
for (int k = 1; k <= 17; k ++ )
{
fa[j][k] = fa[fa[j][k - 1]][k - 1];
int p = fa[j][k - 1];
int d[] = {d1[j][k - 1], d2[j][k - 1], d1[p][k - 1], d2[p][k - 1]};
d1[j][k] = d2[j][k] = -inf;
for (int l = 0; l < 4; l ++ )
if (d[l] > d1[j][k]) d2[j][k] = d1[j][k], d1[j][k] = d[l];
else if (d[l] != d1[j][k] && d[l] > d2[j][k]) d2[j][k] = d[l];
}
}
}
}
}
pair<int, int> lca(int a, int b)
{
int cnt = 0;
int D1 = -inf, D2 = -inf;
if (depth[a] < depth[b]) swap(a, b);
for (int k = 17; k >= 0; k -- )
if (depth[fa[a][k]] >= depth[b])
a = fa[a][k], d[ ++ cnt] = d1[a][k], d[ ++ cnt] = d2[a][k];
if (a != b)
{
for (int k = 17; k >= 0; k -- )
if (fa[a][k] != fa[b][k])
{
d[ ++ cnt] = d1[a][k];
d[ ++ cnt] = d2[a][k];
d[ ++ cnt] = d1[b][k];
d[ ++ cnt] = d2[b][k];
a = fa[a][k];
b = fa[b][k];
}
d[ ++ cnt] = d1[a][0];
d[ ++ cnt] = d2[a][0];
}
for (int i = 1; i <= cnt; i ++ )
if (d[i] > D1) D2 = D1, D1 = d[i];
else if (d[i] != D1 && d[i] > D2) D2 = d[i];
return {D1, D2};
}
signed main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1; i <= m; i ++ )
cin >> edge[i].a >> edge[i].b >> edge[i].w;
for (int i = 1; i <= n; i ++ ) p[i] = i;
int cnt = n - 1, sum = 0;
sort(edge + 1, edge + 1 + m);
for (int i = 1; i <= m; i ++ )
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
int fx = find(a), fy = find(b);
if (fx == fy) continue;
p[fx] = fy;
add(a, b, w), add(b, a, w);
sum += w;
edge[i].st = 1;
cnt -- ;
if (!cnt) break;
}
cout << sum << '\n';
bfs();
int res = inf;
for (int i = 1; i <= m; i ++ )
{
if (edge[i].st) continue;
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
auto [D1, D2] = lca(a, b);
if (D1 < w) res = min(res, sum - D1 + w);
else if (D2 < w) res = min(res, sum - D2 + w);
}
cout << res << '\n';
return 0;
}
P3275 [SCOI2011] 糖果
这题细节的点还是有的。
- 边数应开 3 倍,原来双向边然后加上 0 到每个点的边。
- 判负环时的点数应写成 n+1,因为有 0 的存在。
- 0 到每个点的边权应为 1,而非 0,因为每个人必须分到。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100010, M = 300010;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
bool st[N];
int cnt[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool spfa()
{
stack<int> stk;
stk.push(0);
st[0] = 1;
while (stk.size())
{
int t = stk.top(); stk.pop();
st[t] = 0;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] < dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] > n) return true;
if (!st[j])
{
stk.push(j);
st[j] = 1;
}
}
}
}
return false;
}
signed main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1; i <= m; i ++ )
{
int opt,a, b;
cin >> opt >> a >> b;
if (opt == 1) add(a, b, 0), add(b, a, 0);
else if (opt == 2) add(a, b, 1);
else if (opt == 3) add(b, a, 0);
else if (opt == 4) add(b, a, 1);
else add(a, b, 0);
}
for (int i = 1; i <= n; i ++ ) add(0, i, 1);
if (spfa()) puts("-1"), exit(0);
int res = 0;
for (int i = 1; i <= n; i ++ ) res += dist[i];
cout << res << '\n';
return 0;
}
P4878 [USACO05DEC] Layout G
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = 21010, INF = 0x3f3f3f3f;
int n, m1, m2;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
int cnt[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool spfa(int count)
{
stack<int> stk;
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= count; i ++ )
{
stk.push(i);
dist[i] = 0;
st[i] = true;
}
while (stk.size())
{
int t = stk.top(); stk.pop();
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;
if (!st[j])
{
stk.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m1 >> m2;
for (int i = 1; i < n; i ++ ) add(i + 1, i, 0);
while (m1 -- )
{
int a, b, c;
cin >> a >> b >> c;
if (a > b) swap(a, b);
add(a, b, c);
}
while (m2 -- )
{
int a, b, c;
cin >> a >> b >> c;
if (a > b) swap(a, b);
add(b, a, -c);
}
if (spfa(n)) puts("-1");
else
{
spfa(1);
if (dist[n] == INF) puts("-2");
else cout << dist[n] << '\n';
}
return 0;
}
区间
存在线段树二分 + 贪心的做法,还是不写了。
#include <bits/stdc++.h>
using namespace std;
const int N = 50010, M = 200010;
int n;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void spfa()
{
memset(dist, -0x3f, sizeof dist); // 这里一般还是写初始化为负无穷比较保险,因为可能有负权边。
stack<int> stk;
stk.push(0);
st[0] = 1;
dist[0] = 0;
while (stk.size())
{
int t = stk.top(); stk.pop();
st[t] = 0;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] < dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j])
{
stk.push(j);
st[j] = 1;
}
}
}
}
}
signed main()
{
memset(h, -1, sizeof h);
cin >> n;
for (int i = 1; i <= n; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b + 1, c);
}
for (int i = 1; i < N; i ++ ) add(i - 1, i, 0), add(i, i - 1, -1);
spfa();
cout << dist[N - 1] << '\n';
return 0;
}
距离(tarjan 求 LCA)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 10010, M = N << 1;
int n, m;
int res[M];
struct awa
{
int id, a;
} ;
struct edge
{
int v, w;
} ;
int dist[N];
vector<awa> q[M];
vector<edge> g[N];
int st[N], p[N];
void dfs(int u, int fa)
{
for (auto [v, w] : g[u])
{
if (v == fa) continue;
dist[v] = dist[u] + w;
dfs(v, u);
}
}
int find(int x)
{
if (x == p[x]) return x;
return p[x] = find(p[x]);
}
void tarjan(int u)
{
st[u] = 1;
for (auto [v, w] : g[u])
{
if (!st[v])
{
tarjan(v);
p[v] = u;
}
}
for (auto [id, v] : q[u])
if (st[v] == 2)
res[id] = dist[v] + dist[u] - 2 * dist[find(v)];
st[u] = 2;
}
signed main()
{
cin >> n >> m;
for (int i = 1; i < n; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
g[a].push_back({b, c});
g[b].push_back({a, c});
}
for (int i = 1; i <= m; i ++ )
{
int a, b;
cin >> a >> b;
if (a == b) continue;
q[a].push_back({i, b});
q[b].push_back({i, a});
}
for (int i = 1; i <= n; i ++ ) p[i] = i;
dfs(1, -1);
tarjan(1);
for (int i = 1; i <= m; i ++ ) cout << res[i] << '\n';
return 0;
}
P3008 [USACO11JAN] Roads and Planes G(道路与航线)
yxc 表示这题不难想到,但是我觉得还是有点难想的说。
其实代码实现也不是很难,就是思路比较深。
先把边分为两类,然后不难发现整张图是被分为了很多个团的。
又因为两种边的边权有正负,所以考虑块内dijkstra,然后块之间topo。
复杂度显然能够保证。
然而spfa却不是正解,只是面向数据的优化罢了。
#include <bits/stdc++.h>
using namespace std;
const int N = 25010;
int n, m1, m2, S;
vector<pair<int, int> > g1[N], g2[N];
vector<int> block[N];
int id[N], cnt, in[N];
bool st[N];
int dist[N];
queue<int> q;
void dfs(int u, int bid, int fa)
{
id[u] = bid;
block[bid].push_back(u);
for (auto [v, w] : g1[u])
if (v != fa && !id[v])
dfs(v, bid, u);
}
void dijkstra(int bid)
{
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > heap;
for (int t : block[bid]) heap.push({dist[t], t});
while (heap.size())
{
int u = heap.top().second; heap.pop();
if (st[u]) continue;
st[u] = 1;
for (auto [v, w] : g1[u])
{
if (!( -- in[id[v]])) q.push(id[v]);
if (dist[v] > dist[u] + w)
{
dist[v] = dist[u] + w;
heap.push({dist[v], v});
}
}
for (auto [v, w] : g2[u])
{
if (!( -- in[id[v]])) q.push(id[v]);
if (dist[v] > dist[u] + w)
dist[v] = dist[u] + w;
}
}
}
void topo_sort()
{
memset(dist, 0x3f, sizeof dist);
dist[S] = 0;
for (int i = 1; i <= cnt; i ++ )
if (!in[i]) q.push(i);
while (q.size())
{
int t = q.front(); q.pop();
dijkstra(t);
}
}
signed main()
{
cin >> n >> m1 >> m2 >> S;
for (int i = 1; i <= m1; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
g1[a].push_back({b, c}), g1[b].push_back({a, c});
}
for (int i = 1; i <= n; i ++ )
if (!id[i]) dfs(i, ++ cnt, -1);
for (int i = 1; i <= m2; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
g2[a].push_back({b, c});
in[id[b]] ++ ;
}
topo_sort();
for (int i = 1; i <= n; i ++ )
if (dist[i] > 0x3f3f3f3f / 2) puts("NO PATH");
else cout << dist[i] << '\n';
return 0;
}
P1948 [USACO08JAN] Telephone Lines S(通信线路)
一道最短路好题。
是道蓝题,不过还好。
因为如果你看到题目中最大的最小的话,就不难想到二分的说。
然后判断免费边的话,可能一时之间想不到,把大于设定值的设为1,小于等于的设为0即可。
本题还有另外做法,比较妙,不过不如上面那种。
就是用分层图的说,因为是k条免费边,就可以有k层,每层可以连到上面。
不过这个东西,没怎么学懂,不太会写。
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m, k;
vector<pair<int, int> > g[N];
int l, r, dis[N];
bool st[N];
bool check(int mid)
{
memset(st, 0, sizeof st);
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
deque<int> q;
q.push_back(1);
while (q.size())
{
int t = q.front(); q.pop_front();
if (st[t]) continue;
st[t] = 1;
for (auto [v, w] : g[t])
{
w = (bool)(w > mid);
if (dis[v] > dis[t] + w)
{
dis[v] = dis[t] + w;
if (w) q.push_back(v);
else q.push_front(v);
}
}
}
return dis[n] <= k;
}
signed main()
{
cin >> n >> m >> k;
for (int i = 1; i <= m; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
g[a].push_back({b, c}), g[b].push_back({a, c});
r = max(r, c);
}
int res = -1;
while (l <= r)
{
int mid = l + r >> 1;
if (check(mid)) res = mid, r = mid - 1;
else l = mid + 1;
}
cout << res << '\n';
return 0;
}
不放在每个章节的原因是太懒了。
本文来自博客园,作者:爱朝比奈まふゆ的MafuyuQWQ。 转载请注明原文链接:https://www.cnblogs.com/MafuyuQWQ/p/18864861

浙公网安备 33010602011771号