一个图有 n 个点, m 条无向边,其中第 i 条边的权值是 ci 。
有 q 个询问,每次给定一个区间 [li,ri] ,请你计算出只选择(编号在)这个区间内的边,在使图中连通块数目尽量小的前提之下,选择的边的权值和的最小值。
1≤n≤102,1≤m≤105,1≤q≤1.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
#include
#include
#include
#include
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]bool cmp(int x,int y){return edg[id[x]][2]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;ifor (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]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;
}