洛谷OI_树的刷题笔记

整个笔记注意力惊人,慎入......

持续更新。

P2700 逐个击破

能卡住我的黄题已经很少见了,但这道题确实又是一个。唉,只能说自己依然是蒟蒻吧。

不过,由于题目很容易理解,加上自己因为刷难题身心俱疲,“玩”一下这种简单的题目也算是种放松。

不能因为刷题,把自己学算法的乐趣搞没了。

先上个TLE代码,也就排序那一步和kruskal有点关系。

从权重最小的边开始,判断它所连两结点是否与标记结点相连,如果是,则删去。

但它确实是我自己发明的做法。

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstring>
#define int unsigned long long
using namespace std;

int n, m, k;
int mark[100005] = { 0 };
int cut[100005] = { 0 };
int vis[100005] = { 0 };

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

vector<Edge> e[100005];
vector<Edge> Alledge;

bool cmp(Edge& a, Edge& b) {

    return a.w < b.w;
}

bool islink(int top) {
    if (mark[top]) return true;
    for (auto [u, v, w, id] : e[top]) {
        if (vis[id]) continue;
        vis[id] = 1;
        bool res = islink(v);
        vis[id] = 0;
        if (res) return true;
    }
    return false;
}

signed main()
{
    cin >> n >> k;
    int u, v, w;
    int ans = 0;
    int x;
    for (int i = 1; i <= k; i++) {
        cin >> x;
        mark[x] = 1;
    }
    for (int i = 1; i <= n - 1; i++) {
        cin >> u >> v >> w;
        e[u].push_back({ u,v,w,i });
        e[v].push_back({ v,u,w,i });
        Alledge.push_back({ u,v,w,i });
    }
    sort(Alledge.begin(), Alledge.end(), cmp);

    for (auto [u, v, w, id] : Alledge) {
        vis[id] = 1;
        if (islink(u) and islink(v)) ans += w, k--;
        else vis[id] = 0;
        if (!k) break;
    }

    cout << ans;
    return 0;
}

好的,接下来我们开始卡常。

可以知道开销主要就是在判断是否与标记结点连通这一步。

注意到连接两个标记结点的边必须断开,所以可以先预处理一下:

for (int i = 1; i <= n - 1; i++) {
    cin >> u >> v >> w;
    if (mark[u] and mark[v]) {
        ans += w;
        continue;
    }
    e[u].push_back({ u,v,w,i });
    e[v].push_back({ v,u,w,i });
    Alledge.push_back({ u,v,w,i });
}

还是超时。

注意到

for (auto [u, v, w, id] : Alledge) {
    vis[id] = 1;
    if (islink(u) and islink(v)) ans += w, k--;
    else vis[id] = 0;
    if (!k) break;
}

else vis[id] = 0是不需要的。读者应该不难理解......形象一点说,如果这个边不连接特殊结点,那么后续也就不必通过此边来寻找特殊结点。

还是超时......

注意到有些边是不用进行判断的:如果它与前一个判断的边直接相连,如果前一条边断开了,那么它就一定不连接标记结点的边。

bool islink(int top) {
    if (mark[top]) return true;
    for (auto [u, v, w, id] : e[top]) {
        if (vis[id]) continue;
        vis[id] = 1;
        bool res = islink(v);
        if (res) {
            vis[id] = 0;
            return true;
        }
    }
    return false;
}

居然过了......

那么,这和kruskal算法的关系是什么......

注意到可以先把所有边除掉,然后去连可以连的边,把被标记点视作一个连通域。kruskal算法是不会在连通域间相连的。

连边时,需要先连代价最大的......

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstring>
#define int unsigned long long
using namespace std;

int n, m, k;
int rt[100005] = { 0 };

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

vector<Edge> Alledge;

bool cmp(Edge& a, Edge& b) {

    return a.w > b.w;
}

int findroot(int x) {
    return x == rt[x] ? x : rt[x] = findroot(rt[x]);
}

void un(int x, int y) {
    rt[findroot(x)] = findroot(y);
}

signed main()
{
    cin >> n >> k;
    int u, v, w;
    int ans = 0;
    int x;
    int pre = 0;
    for (int i = 1; i <= n; i++) rt[i] = i;
    for (int i = 1; i <= k; i++) {
        cin >> x;
        if (pre) {
            rt[x] = pre;
        }
        else pre = x;
    }
    for (int i = 1; i <= n - 1; i++) {
        cin >> u >> v >> w;
        ans += w;
        Alledge.push_back({ u,v,w,i });
    }
    sort(Alledge.begin(), Alledge.end(), cmp);
    for (auto [u, v, w, id] : Alledge) {
        int xr = findroot(u), yr = findroot(v);
        if (xr != yr) {
            un(xr, yr);
            ans -= w;
        }
    }
    cout << ans;
    return 0;
}
posted @ 2025-01-22 23:48  minatomorin  阅读(19)  评论(0)    收藏  举报