20250907 NFLS 图论专题(未完结)

图片

直接连边,然后枚举删除哪一个边,判断环即可

点击查看代码
#include <bits/stdc++.h>
using namespace std;

constexpr int MAXN = 110;
constexpr int MAXM = 4010;

struct Edge {
    int to, next;
} e[MAXM];
int head[MAXN], cnt = 0;

void addedge(int u, int v) {
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

bool del_edge[MAXN][MAXN];
int state[MAXN];
int n, m;
vector<pair<int, int>> said[MAXN];

bool dfs_cycle(int u) {
    state[u] = 1;
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if (del_edge[u][v])
            continue;
        if (state[v] == 1)
            return true;
        if (state[v] == 0) {
            if (dfs_cycle(v))
                return true;
        }
    }
    state[u] = 2;
    return false;
}

bool check_candidate(int x) {
    memset(state, 0, sizeof(state));
    memset(del_edge, 0, sizeof(del_edge));
    for (auto &p : said[x]) {
        del_edge[p.first][p.second] = true;
    }
    for (int i = 1; i <= n; ++i) {
        if (state[i] == 0) {
            if (dfs_cycle(i))
                return false;
        }
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        int a, b, c;
        cin >> a >> b >> c;
        addedge(b, c);
        said[a].push_back({ b, c });
    }
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        if (check_candidate(i))
            ++ans;
    }
    cout << ans << '\n';
    return 0;
}


图片

发现每个点被算通过的边次数此,所以边权是两边的点权+边权*2。起点发现可以任取

点击查看代码
#include <bits/stdc++.h>
using namespace std;
long long n;
constexpr long long maxn = 1e5 + 10;
struct DSU {
    long long fa[maxn];
    DSU(long long n_) {
        for (long long i = 1; i <= n; i++) {
            fa[i] = i;
        }
    }
    long long find(long long x) {
        if (fa[x] == x)
            return x;
        return fa[x] = find(fa[x]);
    }
    void merge(long long x, long long y) {
        x = find(x), y = find(y);
        fa[x] = y;
    }
};

struct edge {
    int u, v, w;
    edge(int u_, int v_, int w_) : u(u_), v(v_), w(w_) {}
};

int main() {
    ios::sync_with_stdio(0);
    long long p;
    cin >> n >> p;
    vector<long long> c(n + 1);
    long long mnc = 2147483647;
    for (long long i = 1; i <= n; i++) {
        cin >> c[i];
        mnc = min(mnc, c[i]);
    }

    vector<edge> es;
    for (long long i = 1; i <= p; i++) {
        long long s, e, l;
        cin >> s >> e >> l;
        long long w = 2 * l + c[s] + c[e];
        es.emplace_back(s, e, w);
    }
    sort(es.begin(), es.end(), [](const edge &a, const edge &b) { return a.w < b.w; });

    DSU dsu(n);
    long long mst = 0;
    long long used = 0;
    for (const auto x : es) {
        if (dsu.find(x.u) != dsu.find(x.v)) {
            mst += x.w;
            ++used;
            dsu.merge(x.u, x.v);
        }
    }
    assert(used >= n - 1);
    long long ans = mst + mnc;
    cout << ans << endl;
    return 0;
}

图片

先把边权定为min(K-w,0),然后跑MST,发现有可能不优秀。

直接对所有边做一次 Kruskal 得到最小的 “降权” 和;如果生成树中已有 ≥k 的边,则即为最优。
否则,两种选择取最小值:
    仅用所有 <k 的边连通时需要的 “提权” 操作:k−max_edge_in_light_MST
    强制“预选”一条超限边(取多余量最小者),再做 Kruskal,得到降权和
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct DSU {
    int n;
    vector<int> p, r;
    DSU(int _n) : n(_n), p(n + 1), r(n + 1, 0) { iota(p.begin(), p.end(), 0); }
    int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
    bool unite(int a, int b) {
        a = find(a);
        b = find(b);
        if (a == b)
            return false;
        if (r[a] < r[b])
            swap(a, b);
        p[b] = a;
        if (r[a] == r[b])
            r[a]++;
        return true;
    }
};

struct Edge {
    int u, v;
    ll s;
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int T;
    cin >> T;
    while (T--) {
        int n, m;
        ll k;
        cin >> n >> m >> k;
        vector<Edge> edges(m);
        for (int i = 0; i < m; i++) {
            cin >> edges[i].u >> edges[i].v >> edges[i].s;
        }

        auto cmpAll = [&](const Edge &a, const Edge &b) {
            ll wa = max(0LL, a.s - k), wb = max(0LL, b.s - k);
            if (wa != wb)
                return wa < wb;
            return a.s > b.s;
        };
        vector<Edge> allE = edges;
        sort(allE.begin(), allE.end(), cmpAll);

        DSU dsu1(n);
        ll sum1 = 0;
        bool hasHeavy1 = false;
        ll maxInTree1 = 0;
        int cnt1 = 0;
        for (auto &e : allE) {
            if (dsu1.unite(e.u, e.v)) {
                cnt1++;
                if (e.s > k) {
                    sum1 += (e.s - k);
                    hasHeavy1 = true;
                }
                maxInTree1 = max(maxInTree1, e.s);
                if (cnt1 == n - 1)
                    break;
            }
        }

        ll ans;
        if (hasHeavy1) {
            ans = sum1;
        } else {
            ll costA = k - maxInTree1;
            ll bestExtra = LLONG_MAX;
            int idxBest = -1;
            for (int i = 0; i < m; i++) {
                if (edges[i].s >= k) {
                    ll w1 = edges[i].s - k;
                    if (w1 < bestExtra) {
                        bestExtra = w1;
                        idxBest = i;
                    }
                }
            }
            ll costB = LLONG_MAX;
            if (idxBest != -1) {
                DSU dsu2(n);
                ll sum2 = bestExtra;
                int cnt2 = 0;
                dsu2.unite(edges[idxBest].u, edges[idxBest].v);
                cnt2++;
                for (auto &e : allE) {
                    if (dsu2.unite(e.u, e.v)) {
                        cnt2++;
                        if (e.s > k) {
                            sum2 += (e.s - k);
                        }
                        if (cnt2 == n - 1)
                            break;
                    }
                }
                if (cnt2 == n - 1)
                    costB = sum2;
            }

            ans = min(costA, costB);
        }

        cout << ans << "\n";
    }

    return 0;
}

图片

直接S和T分别跑spfa即可。

注意不可达的边spfa不要走,因为如果这时候出现负环,你是无法判断负环是否可达终点的。

点击查看代码

#include <bits/stdc++.h>
using namespace std;

constexpr int MAXN = 10000 + 10;
constexpr int MAXM = 200000 + 10;
constexpr int INF = 1e9;

struct Edge {
    int to, next, val;
} e[MAXM];

int head[MAXN], cnt;
int dis[MAXN], vis[MAXN], viscnt[MAXN];
bool reachable[MAXN];
queue<int> q;

vector<int> revAdj[MAXN];

void addEdge(int u, int v, int w) {
    e[++cnt].to = v;
    e[cnt].val = w;
    e[cnt].next = head[u];
    head[u] = cnt;
    revAdj[v].push_back(u);
}

void dfs(int n, int T) {
    fill(reachable, reachable + n + 1, false);
    queue<int> rq;
    reachable[T] = true;
    rq.push(T);
    while (!rq.empty()) {
        int u = rq.front();
        rq.pop();
        for (int v : revAdj[u]) {
            if (!reachable[v]) {
                reachable[v] = true;
                rq.push(v);
            }
        }
    }
}

bool spfa(int n, int S) {
    for (int i = 0; i <= n; i++) {
        dis[i] = INF;
        vis[i] = 0;
        viscnt[i] = 0;
    }
    while (!q.empty()) q.pop();

    dis[S] = 0;
    vis[S] = 1;
    q.push(S);

    while (!q.empty()) {
        int u = q.front();
        q.pop();
        vis[u] = 0;

        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].to;
            int w = e[i].val;
            if (!reachable[v])
                continue;

            if (dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                viscnt[v] = viscnt[u] + 1;
                if (viscnt[v] >= n) {
                    return false;
                }
                if (!vis[v]) {
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
    }
    return true;
}

void solve() {
    int n, m, Tcase;
    cin >> n >> m >> Tcase;

    cnt = 0;
    for (int i = 0; i <= n; i++) {
        head[i] = 0;
        revAdj[i].clear();
    }

    vector<tuple<int, int, int>> edges;
    edges.reserve(m);
    for (int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        edges.emplace_back(u, v, w);
    }

    int S, T;
    cin >> S >> T;

    for (auto &ed : edges) {
        int u, v, w;
        tie(u, v, w) = ed;
        addEdge(u, v, w);
    }
    int ans = 0;
    dfs(n, T);
    if (!spfa(n, S)) {
        ans++;
    } else if (dis[T] <= -Tcase) {
        ans++;
    }
    swap(S, T);
    dfs(n, T);
    if (!spfa(n, S)) {
        ans++;
    } else if (dis[T] <= -Tcase) {
        ans++;
    }
    if (ans >= 1) {
        cout << "yes" << endl;
    } else {
        cout << "no" << endl;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int TT;
    cin >> TT;
    while (TT--) {
        solve();
    }
    return 0;
}

图片
图片

直接考虑连边

图片

然后跑spfa,如果有负环,直接无解,但是会被卡到NM。

考虑把相等的用并查集缩点,然后跑即可

点击查看代码


#include <bits/stdc++.h>
using namespace std;
#define int long long

struct Edge {
    int to, next, val;
};

constexpr int maxn = 1e6 + 10;

int n, m, cnt;
int head[maxn];
Edge e[maxn];
vector<int> fa;
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void merge(int a, int b) {
    a = find(a);
    b = find(b);
    if (a != b)
        fa[a] = b;
}

void addedge(int u, int v, int w) {
    e[++cnt].to = v;
    e[cnt].next = head[u];
    e[cnt].val = w;
    head[u] = cnt;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    int k;
    cin >> n >> k;
    fa.resize(n + 1);
    for (int i = 1; i <= n; i++) fa[i] = i;

    vector<array<int, 3>> rest;
    for (int i = 1; i <= k; i++) {
        int x, a, b;
        cin >> x >> a >> b;
        if (a < 1 || a > n || b < 1 || b > n) {
            cout << -1 << "\n";
            return 0;
        }
        if (x == 1) {
            merge(a, b);
        } else {
            rest.push_back({ x, a, b });
        }
    }

    for (auto &c : rest) {
        c[1] = find(c[1]);
        c[2] = find(c[2]);
        if (c[1] == c[2]) {
            /*
            int x=c[0],a=c[1],b=c[2];
            if(x==2||x==4){
            cout << -1 << "\n";
            return 0;
            }
            */
        }
    }

    for (auto [x, a, b] : rest) {
        if (x == 2) {  // a < b
            addedge(a, b, 1);
        } else if (x == 3) {  // a >= b
            addedge(b, a, 0);
        } else if (x == 4) {  // a > b
            addedge(b, a, 1);
        } else if (x == 5) {  // a <= b
            addedge(a, b, 0);
        }
    }

    vector<int> d(n + 1, 1);
    vector<int> inq(n + 1, 0), viscnt(n + 1, 0);
    queue<int> q;

    for (int i = 1; i <= n; i++)
        if (find(i) == i) {
            q.push(i);
            inq[i] = 1;
            viscnt[i] = 1;
        }

    while (!q.empty()) {
        int u = q.front();
        q.pop();
        inq[u] = 0;
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].to, w = e[i].val;
            if (d[v] < d[u] + w) {
                d[v] = d[u] + w;
                if (!inq[v]) {
                    q.push(v);
                    inq[v] = 1;
                    viscnt[v]++;
                    if (viscnt[v] > n) {
                        cout << -1 << "\n";
                        return 0;
                    }
                }
            }
        }
    }

    int ans = 0;
    for (int i = 1; i <= n; i++) ans += d[find(i)];
    cout << ans << "\n";

    return 0;
}


图片

图片

考虑从起点再往外走一条边,跑最短路,如果不再经过那一条边就是答案之一。
把起点出边编号,然后按位二进制枚举不同的集合,给边定向然后跑log次最短路。


图片
图片

把一个边拆成两个点,考虑一个点,一个菊花(连了若干条边,我们取若干条边的靠近这个点的哪一个分成的点)。
直接n^2连边可行,但是太慢了。

考虑将边权(点权)排序,然后相邻的两个权值的点,升序边权为差值,降序边权为0,类似一个环岛,升高要补油,降低不给油,也就满足最大值的定义了。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = (ll)4e18;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    if (!(cin >> n >> m))
        return 0;

    vector<int> au(m), bu(m);
    vector<ll> w(m);
    for (int i = 0; i < m; ++i) {
        int u, v;
        ll c;
        cin >> u >> v >> c;
        au[i] = u;
        bu[i] = v;
        w[i] = c;
    }

    int nodes = 2 * m;
    vector<vector<pair<int, ll>>> g(nodes);

    vector<vector<pair<ll, int>>> out_at(n + 1);
    for (int i = 0; i < m; ++i) {
        int u = au[i], v = bu[i];
        out_at[u].push_back({ w[i], 2 * i });
        out_at[v].push_back({ w[i], 2 * i + 1 });
    }

    for (int v = 1; v <= n; ++v) {
        if (out_at[v].empty())
            continue;
        auto &lst = out_at[v];
        sort(lst.begin(), lst.end());
        int k = (int)lst.size();
        for (int j = 0; j + 1 < k; ++j) {
            ll wj = lst[j].first;
            ll wj1 = lst[j + 1].first;
            int idj = lst[j].second;
            int idj1 = lst[j + 1].second;
            g[idj].push_back({ idj1, wj1 - wj });
            g[idj1].push_back({ idj, 0 });
        }
    }

    for (int i = 0; i < m; ++i) {
        int a = 2 * i;
        int b = 2 * i + 1;
        g[a].push_back({ b, w[i] });
        g[b].push_back({ a, w[i] });
    }

    vector<ll> dist(nodes, INF);
    priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> pq;
    for (int i = 0; i < m; ++i) {
        if (au[i] == 1) {
            int id = 2 * i;  // 1->bu[i]
            if (dist[id] > w[i]) {
                dist[id] = w[i];
                pq.push({ dist[id], id });
            }
        }
        if (bu[i] == 1) {
            int id = 2 * i + 1;  // 1->au[i]
            if (dist[id] > w[i]) {
                dist[id] = w[i];
                pq.push({ dist[id], id });
            }
        }
    }

    while (!pq.empty()) {
        auto cur = pq.top();
        pq.pop();
        ll d = cur.first;
        int u = cur.second;
        if (d != dist[u])
            continue;
        for (auto &ec : g[u]) {
            int v = ec.first;
            ll c = ec.second;
            if (dist[v] > d + c) {
                dist[v] = d + c;
                pq.push({ dist[v], v });
            }
        }
    }
    ll ans = INF;
    for (int i = 0; i < m; ++i) {
        if (bu[i] == n) {
            int id = 2 * i;
            if (dist[id] < INF)
                ans = min(ans, dist[id] + w[i]);
        }
        if (au[i] == n) {
            int id = 2 * i + 1;
            if (dist[id] < INF)
                ans = min(ans, dist[id] + w[i]);
        }
    }

    if (ans == INF)
        cout << "-1\n";
    else
        cout << ans << "\n";
    return 0;
}


图片
图片


图片

然后直接用并查集,枚举每一个权值,把w以上的边都并起来,最后累加答案,2e8,虽然computer is fast now,但是南外机子跑不过是理所当然的

点击查看代码
#include <bits/stdc++.h>
using namespace std;
struct Edge {
    int u, v, w;
};

struct DSU {
    int n;
    vector<int> fa, sz;
    DSU(int n = 0) { init(n); };

    void init(int n_) {
        n = n_;
        fa.assign(n + 1, 0);
        sz.assign(n + 1, 1);
        for (int i = 1; i <= n; i++) fa[i] = i;
    }

    int find(int x) {
        if (x == fa[x])
            return x;
        return fa[x] = find(fa[x]);
    }
    void merge(int a, int b) {
        a = find(a), b = find(b);
        if (a == b)
            return;
        if (sz[a] < sz[b])
            swap(a, b);
        fa[b] = a;
        sz[a] += sz[b];
    }
};

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    int n;
    cin >> n;
    vector<Edge> edges;
    int maxw = 0;
    edges.reserve(n - 1);
    for (int i = 1; i < n; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        edges.push_back({ u, v, w });
        if (w > maxw)
            maxw = w;
    }

    vector<int> ans(n + 1, 0);
    for (int t = maxw; t > 0; t--) {
        DSU dsu(n);
        for (Edge &e : edges) {
            if (e.w >= t)
                dsu.merge(e.u, e.v);
        }
        for (int i = 1; i <= n; i++) {
            int root = dsu.find(i);
            ans[i] += dsu.sz[root] - 1;
        }
    }

    int res = 0;
    for (int i = 1; i <= n; i++) res = max(res, ans[i]);
    cout << res << endl;
    return 0;
}

瓶颈在于重建并查集。发现如果按边权排序,直接从大到小维护也可以。

可以通过

点击查看代码
#include <bits/stdc++.h>
using namespace std;

struct Edge {
    int u, v, w;
};

struct DSU {
    int n;
    vector<int> fa, sz;
    DSU(int n = 0) { init(n); }
    void init(int n_) {
        n = n_;
        fa.resize(n + 1);
        sz.assign(n + 1, 1);
        for (int i = 1; i <= n; i++) fa[i] = i;
    }
    int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
    void merge(int a, int b) {
        a = find(a), b = find(b);
        if (a == b)
            return;
        if (sz[a] < sz[b])
            swap(a, b);
        fa[b] = a;
        sz[a] += sz[b];
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    vector<Edge> edges;
    edges.reserve(n - 1);

    int maxw = 0;
    for (int i = 1; i < n; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        edges.push_back({ u, v, w });
        maxw = max(maxw, w);
    }

    sort(edges.begin(), edges.end(), [](auto &a, auto &b) { return a.w > b.w; });

    DSU dsu(n);
    vector<long long> ans(n + 1, 0);

    int idx = 0;
    int curw = maxw;
    long long res = 0;
    while (curw > 0) {
        while (idx < (int)edges.size() && edges[idx].w == curw) {
            dsu.merge(edges[idx].u, edges[idx].v);
            idx++;
        }
        int nextw = (idx < (int)edges.size() ? edges[idx].w : 0);

        int len = curw - nextw;
        if (len > 0) {
            for (int i = 1; i <= n; i++) {
                int root = dsu.find(i);
                ans[i] += 1LL * len * (dsu.sz[root] - 1);
                res = max(res, ans[i]);
            }
        }
        curw = nextw;
    }

    cout << res << "\n";
    return 0;
}

posted @ 2025-09-07 21:14  Dreamers_Seve  阅读(8)  评论(0)    收藏  举报