2021牛客寒假算法基础集训营6 H.动态最小生成树 (线段树维护最小生成树边)

传送门
(这题居然现场写出来了 )

题意

小 Z 喜欢最小生成树。
小 Z 有一张 nn 个点 mm 条边的图,每条边连接点 u i , v i ​ u_i,v_i ​ ui,vi,边权为 w i w_i wi。他想进行 q q q 次操作,有如下两种类型:
1.修改第 x x x 条边为连接点 y , z y,z y,z ,边权为 t t t
2.查询只用编号在 [ l , r ] [l,r] [l,r]范围内的边,得到的最小生成树权值是多少。
对于每次询问,输出最小生成树权值。如果无解,输出 Impossible 。
1 ≤ n ≤ 200 , 1 ≤ m ≤ 30000 , 1 ≤ q ≤ 30000 , 1 ≤ u i , v i ≤ n , 1 ≤ w i ≤ 100000 。 1≤n≤200,1≤m≤30000,1≤q≤30000,1≤u_i ,v_i≤n,1≤w_i≤100000。 1n200,1m30000,1q30000,1ui,vin,1wi100000
1 ≤ x ≤ m , 1 ≤ y , z ≤ n , 1 ≤ t ≤ 100000 。 1\le x\le m,1\le y,z\le n,1\le t\le 100000。 1xm,1y,zn,1t100000

分析

区间问题,优先考虑线段树(虽然这题暴力也能过
然后就能想到,线段树区间维护最小生成树所用到的边,是可以进行pushup的。
pushup操作具体为对两个儿子维护的边集做Kruskal,把结果存进父节点的边集中。
而且这颗线段树是单点更新的,不需要维护懒标记~
核心代码

struct edges
{
    int u, v, w;
} edge[M];
struct node
{
    int l, r;
    vector<int> seq;//边集:最小生成树所用到的边的编号(限制在[l~r]中)
} tr[M << 2];

int find(int x)
{
    if (x != fa[x])
        fa[x] = find(fa[x]);
    return fa[x];
}

bool Union(int a, int b)
{
    a = find(a), b = find(b);
    if (a == b)
        return false;
    fa[a] = b;
    return true; //成功进行了一次合并
}
void pushup(vector<int> &s1, vector<int> &s2, vector<int> &s3) //很厉害的合并操作
{
    static vector<int> tmp; //归并排序进行Kruskal,看大佬代码学的
    init();                 //初始化并查集数组	fa[i]=i
    tmp.clear();
    int i = 0, j = 0;
    while (i < s2.size() || j < s3.size())
    {
        if (j >= s3.size() || (i < s2.size() && edge[s2[i]].w < edge[s3[j]].w))
        {
            if (Union(edge[s2[i]].u, edge[s2[i]].v)) //如果Union操作成功进行了一次合并
                tmp.push_back(s2[i]);
            i++;
        }
        else
        {
            if (Union(edge[s3[j]].u, edge[s3[j]].v)) //如果Union操作成功进行了一次合并
                tmp.push_back(s3[j]);
            j++;
        }
    }
    s1 = tmp;
}

代码

#include <bits/stdc++.h>

using namespace std;
//-----pre_def----
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
#define fir(i, a, b) for (int i = (a); i <= (b); i++)
#define rif(i, a, b) for (int i = (a); i >= (b); i--)
#define endl '\n'
#define init_h memset(h, -1, sizeof h), idx = 0;
#define lowbit(x) x &(-x)

//---------------
const int N = 210, M = 3e4 + 10;
int n, m, q, u, v, w;
int fa[N];
vector<int> ans;
void init()
{
    fir(i, 1, n) fa[i] = i;
}
struct edges
{
    int u, v, w;
} edge[M];
struct node
{
    int l, r;
    vector<int> seq;
} tr[M << 2];

int find(int x)
{
    if (x != fa[x])
        fa[x] = find(fa[x]);
    return fa[x];
}

bool Union(int a, int b)
{
    a = find(a), b = find(b);
    if (a == b)
        return false;
    fa[a] = b;
    return true; //成功进行了一次合并
}

void pushup(vector<int> &s1, vector<int> &s2, vector<int> &s3) //很厉害的合并操作
{
    static vector<int> tmp; //归并排序进行Kruskal
    init();                 //初始化并查集数组
    tmp.clear();
    int i = 0, j = 0;
    while (i < s2.size() || j < s3.size())
    {
        if (j >= s3.size() || (i < s2.size() && edge[s2[i]].w < edge[s3[j]].w))
        {
            if (Union(edge[s2[i]].u, edge[s2[i]].v)) //如果Union操作成功进行了一次合并
                tmp.push_back(s2[i]);
            i++;
        }
        else
        {
            if (Union(edge[s3[j]].u, edge[s3[j]].v)) //如果Union操作成功进行了一次合并
                tmp.push_back(s3[j]);
            j++;
        }
    }
    s1 = tmp;
}

void pushup(int u)
{
    pushup(tr[u].seq, tr[u << 1].seq, tr[u << 1 | 1].seq);
}

void build(int u, int l, int r)
{
    tr[u].l = l, tr[u].r = r;
    if (l == r)
    {
        tr[u].seq.push_back(l); //节点中维护最小生成树的边(分析可知,父节点的信息可由子节点推出)
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    pushup(u); //合并操作操作
}

void modify(int u, int l, int r)
{
    if (l <= tr[u].l && tr[u].r <= r)
    {
        tr[u].seq.clear();
        tr[u].seq.push_back(l); //l为当前点的编号
        return;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid)
        modify(u << 1, l, r);
    if (mid < r)
        modify(u << 1 | 1, l, r);
    pushup(u);
}
void query(int u, int l, int r)
{
    if (l <= tr[u].l && tr[u].r <= r)
    {
        pushup(ans, ans, tr[u].seq);
        return;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid)
        query(u << 1, l, r);
    if (mid < r)
        query(u << 1 | 1, l, r);
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    int StartTime = clock();
#endif
    scanf("%d%d%d", &n, &m, &q);
    fir(i, 1, m)
    {
        scanf("%d%d%d", &u, &v, &w);
        edge[i] = {u, v, w};
    }
    build(1, 1, m);
    for (int i = 1; i <= q; i++)
    {
        int op;
        scanf("%d", &op);
        if (op == 2)
        {
            int l, r;
            scanf("%d%d", &l, &r);
            ans.clear();
            int sum = 0;
            query(1, l, r);
            if (ans.size() != n - 1)
            {
                puts("Impossible");
            }
            else
            {
                for (auto t : ans)
                    sum += edge[t].w;
                printf("%d\n", sum);
            }
        }
        else
        {
            int x, y, z, t;
            scanf("%d%d%d%d", &x, &y, &z, &t);
            edge[x] = {y, z, t};
            modify(1, x, x);
        }
    }
#ifndef ONLINE_JUDGE
    printf("Run_Time = %d ms\n", clock() - StartTime);
#endif
    return 0;
}

功能强大的线段树

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值