每日一题 牛客基础 第六场 动态最小生成树 最小生成树 线段树

每日一题 牛客基础 第六场 动态最小生成树 最小生成树 线段树


在这里插入图片描述
在这里插入图片描述
好妙的一题啊,自己是不太能想出来的,其中有几个点。第一个是,区间查询,那应该想到线段树,线段树的每个区间里面存这个区间的边能构成的最小生成树,当然不一定是一颗完整联通的树。然后就是区间合并的问题了,怎么把两个子区间合并成一个大区间呢,其实就是把两个子区间各自存下来的边放在一起,再跑一遍最小生成树,把边存下来。
在合并的时候有个技巧,因为两个子区间存下来的边都是从小到大排好序的,那大区间把这些边加进来的时候可以用归并排序的方式,设两个指针,每次从左右区间选一个小的边加进来,这样大区间的边排序的复杂度就是On的,之后再用kruskal算法求出最小生成树。
你可能会问,每次更新区间要跑一遍最小生成树,不会超时吗?但是看数据,n<200,所以每个子区间最多存199条边,大区间最多只需要398条边来生成树,所以代价还是不大的。
题目给了5s,我的代码跑了0.95s,时间算是非常充裕的。

#include<bits/stdc++.h>
using namespace std;
int n,m,q;
typedef vector<int> vec;
int dsu[205];
int fnd(int x)
{
    if(dsu[x]==x)return x;
    return dsu[x]=fnd(dsu[x]);
}
int merg(int x,int y)
{
    x=fnd(x),y=fnd(y);
    dsu[x]=y;
    return x!=y;
}
struct EDGE
{
    int a,b,w;
}e[30005];
struct node
{
    int l,r,v;
    vec ve;
} sgt[120005];
void pushup(int &v,vec &u,vec &a,vec &b)
{
    v=0;
    for(int i=1;i<205;i++)dsu[i]=i;
    int i=0,j=0;
    u.clear();
    while(i<a.size()||j<b.size())
    {
        if(i<a.size()&&j<b.size())
        {
            if(e[a[i]].w<e[b[j]].w)
            {
                if(merg(e[a[i]].a,e[a[i]].b))
                    u.push_back(a[i]),v+=e[a[i]].w;
                i++;
            }
            else
            {
                if(merg(e[b[j]].a,e[b[j]].b))
                    u.push_back(b[j]),v+=e[b[j]].w;
                j++;
            }
        }
        else if(i<a.size())
        {
            if(merg(e[a[i]].a,e[a[i]].b))
                    u.push_back(a[i]),v+=e[a[i]].w;
            i++;
        }
        else if(j<b.size())
        {
            if(merg(e[b[j]].a,e[b[j]].b))
                    u.push_back(b[j]),v+=e[b[j]].w;
            j++;
        }
    }
}
void iniSgt(int p,int l,int r)
{
    sgt[p].l=l;
    sgt[p].r=r;
    if(l==r)
    {
        sgt[p].ve.push_back(r);
        sgt[p].v=e[r].w;
        return;
    }
    int mid=(l+r)/2;
    iniSgt(p*2,l,mid);
    iniSgt(p*2+1,mid+1,r);
    pushup(sgt[p].v,sgt[p].ve,sgt[p*2].ve,sgt[p*2+1].ve);
}
void modify(int p,int X)
{
    int l=sgt[p].l,r=sgt[p].r;
    if(l==r&&l==X)
    {
        sgt[p].v=e[X].w;
        return;
    }
    int mid=(l+r)/2;
    if(X<=mid)
        modify(p*2,X);
    if(X>=mid+1)
        modify(p*2+1,X);
    pushup(sgt[p].v,sgt[p].ve,sgt[p*2].ve,sgt[p*2+1].ve);
}
int query(int p,int x,int y,vec &u)
{
    int l=sgt[p].l,r=sgt[p].r;
    if(x<=l&&y>=r)
    {
        vec tmp=u;
        int t=0;
        pushup(t,u,tmp,sgt[p].ve);
        return t;
    }
    int mid=(l+r)/2,res=0;
    if(x<=mid)
        res=query(p*2,x,y,u);
    if(y>mid)
        res=query(p*2+1,x,y,u);
    return res;//用两个儿子更新爸爸
}
int cnt[205];
int main()
{
    cin>>n>>m>>q;
    for(int i=1;i<=m;i++)
        cin>>e[i].a>>e[i].b>>e[i].w;
    iniSgt(1,1,m);
    while(q--)
    {
        int opt;cin>>opt;
        if(opt==1)
        {
            int x,y,z,t;cin>>x>>y>>z>>t;
            e[x].a=y,e[x].b=z,e[x].w=t;
            modify(1,x);
        }
        else
        {
            int l,r;cin>>l>>r;
            vec t;int tot=0;
            int ans=query(1,l,r,t);
            if(t.size()==n-1)cout<<ans<<endl;
            else cout<<"Impossilbe"<<endl;
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值