[JZOJ5060]公路建设

23 篇文章 1 订阅
10 篇文章 0 订阅

题目大意

一个图有 n 个点,m条无向边,其中第 i 条边的权值是ci
q 个询问,每次给定一个区间[li,ri],请你计算出只选择(编号在)这个区间内的边,在使图中连通块数目尽量小的前提之下,选择的边的权值和的最小值。

1n102,1m105,1q1.5×104


题目分析

显然题目是求区间内的边的最小生成森林的边权和。
可以发现最小生成树(森林)具有可合并性。
一个很显然的想法:将所有边按照编号分块,设每一块大小为 B ,令fi,j表示从第 i 个块到第j个块的边构成的最小生成森林的边集,这个可以 O(n(mB)2) 的时间复杂度完成。
然后询问时直接拿出跨块的 f 值以及两边多出来的边合并一下就好了。
B m 的话,时间复杂度可以做到 O(nm+q(n+m)(log(n+m)+α(n))) (如果你实现得好一点,用归并排序可以省掉 log )。
不过这个方法还是太naive了,我们把分块换成线段树就可以在 O(n(m+q)logmα(n)) 的时间复杂度内解决问题。


代码实现

于是我依然特别naive地打了分块。

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
#include <cmath>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

int buf[30];

void write(int x)
{
    if (x<0) putchar('-'),x=-x;
    for (;x;x/=10) buf[++buf[0]]=x%10;
    if (!buf[0]) buf[++buf[0]]=0;
    for (;buf[0];putchar('0'+buf[buf[0]--]));
}

const int N=105;
const int M=100005;
const int B=500;

int srt[M],id[M];
int fa[N],rank[N];
int mst[B][B][N];
int st[B],en[B];
int edg[M][3];
int n,m,q,cnt,bs,bcnt;

int getfather(int son){return fa[son]==son?son:fa[son]=getfather(fa[son]);}

void merge(int x,int y)
{
    if (rank[x]<rank[y]) swap(x,y);
    fa[y]=x,rank[x]+=rank[x]==rank[y];
}

bool cmp(int x,int y){return edg[id[x]][2]<edg[id[y]][2];}

int Kruscal(int *e)
{
    e[0]=0;
    int ret=0;
    for (int i=1;i<=cnt;++i) srt[i]=i;
    sort(srt+1,srt+1+cnt,cmp);
    for (int i=1;i<=n;++i) fa[i]=i,rank[i]=0;
    for (int i=1,j,x,y;i<=cnt;++i)
    {
        j=id[srt[i]],x=getfather(edg[j][0]),y=getfather(edg[j][1]);
        if (x!=y) merge(x,y),ret+=edg[j][2],e[++e[0]]=j;
    }
    return ret;
}

void block()
{
    bs=trunc(sqrt(m));
    for (int l=1,r;l<=m;l=r+1)
    {
        st[++bcnt]=l,en[bcnt]=r=min(m,l+bs-1),cnt=0;
        for (int i=l;i<=r;++i) id[++cnt]=i;
        Kruscal(mst[bcnt][bcnt]);
    }
    for (int i=1;i<bcnt;++i)
        for (int j=i+1;j<=bcnt;++j)
        {
            cnt=0;
            for (int k=1;k<=mst[i][j-1][0];++k) id[++cnt]=mst[i][j-1][k];
            for (int k=1;k<=mst[j][j][0];++k) id[++cnt]=mst[j][j][k];
            Kruscal(mst[i][j]);
        }
}

int main()
{
    freopen("highway.in","r",stdin),freopen("highway.out","w",stdout);
    n=read(),m=read(),q=read();
    for (int i=1;i<=m;++i)
        for (int j=0;j<3;++j)
            edg[i][j]=read();
    block();
    for (int l,r,lid,rid;q--;printf("%d\n",Kruscal(mst[0][0])))
    {
        l=read(),r=read(),lid=rid=0,cnt=0;
        for (int i=1;i<=bcnt;++i)
        {
            if (en[i]<l) continue;
            if (st[i]>r) break;
            if (st[i]<=l) for (int j=l;j<=en[i]&&j<=r;++j) id[++cnt]=j;
            else if (en[i]>=r) for (int j=st[i];j<=r;++j) id[++cnt]=j;
            else
            {
                if (!lid) lid=i;
                rid=i;
            }
        }
        if (lid) for (int i=1;i<=mst[lid][rid][0];++i) id[++cnt]=mst[lid][rid][i];
    }
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值