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;
}

不放在每个章节的原因是太懒了

posted @ 2025-05-07 18:14  MafuyuQWQ  阅读(30)  评论(0)    收藏  举报