【省选十连测之一】【线段树】【最小生成树之Kruskal】公路建设
题意
有n个点,m条双向道路,其中第i条公路的两个端点是u[i],v[i],费用是c[i]。
现在给出q个询问,每次给定一个L和一个R,要求你只能够使用[L,R]这个区间内的边,使得连接之后,连通块的数量最小。在保证连通块数量最小的情况下,求最少需要的代价(可以拿一些边不用)。
输入格式
第一行三个整数n,m,q,含义如图所示
接下来m行,每行3个整数,描述一条边,分别是u,v,c。
接下来q行,每行2个整数L,R,表示一次询问。
输出格式
对于每一个询问,输出一个整数表示最小连通块意义下的最小代价。
数据范围
n<=100,m<100000,q<=15000
思路
这道题还正是神奇...
看完题目之后,不难得出题目的要求就是让我们用[L,R]之间的边求一个最小生成树。然后我们就有了一个\(O(m*q*\log m )\)的暴力算法。
然而在考场上,自己脑残了...想了几分钟之后,这不是...回滚莫队吗?正好可以去掉难搞的删除操作,然后...再用LCT来回答LCA...好像不可做欸...然后全程想怎么优化...然后就...GG。
看完题解之后,发现好像以前见过类似的做法,但是...似乎又没有...
题解的大致思路就是:
对于m条边建一个线段树,下标就是边的标号,然后对于一个线段树区间[L,R],这个节点上存的是使用[L,R]这个区间的边,求得的最小生成树使用了的边的编号(用数组存在线段树结点里)。因为最多会使用n-1条边,那么就最多会存\(O(m * 4 *n)\)个数字,是不会炸的。
然后怎么合并呢?因为我们一个节点里最多只会塞n-1条边(体现为编号),那么我们就可以直接暴力合并!也就是用Kruskal暴力合并即可。但需要注意的是,不能直接把当前线段树节点的左右儿子的边集直接存下来然后排序跑Kruskal,而应该利用归并排序的思想,直接用双指针在左右两个儿子的边集中扫一下就可以了。
然后处理询问的时候就利用线段树的结构,将询问的区间拆成\(\log m\)个子区间,然后暴力合并即可。
最后总的时间复杂度就是\(O((m\log m+q\log m)n*f(n))\),其中f(n)代表的是n个点的路径压缩版本的并查集的复杂度。
去你的回滚莫队
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 100
#define MAXM 100000
#define MAXQ 15000
using namespace std;
struct edge
{
int u,v,w;
edge(){};
edge(int _u,int _v,int _w):u(_u),v(_v),w(_w){};
}edges[MAXM+5];
bool operator < (const edge &a,const edge &b)
{return a.w<b.w;}
bool cmp(const int &a,const int &b)
{return edges[a]<edges[b];}
struct TreeNode
{
int seq[MAXN*2+5];//第0位存长度
}tree[MAXM*4];
int n,m,q,setfa[MAXN+5];
int ANS[MAXN*2+5],ANSval;//答案用的边集ANS[],以及最小代价ANSval
int Findfa(int x)
{
if(setfa[x]==x)
return x;
return setfa[x]=Findfa(setfa[x]);
}
bool Union(int x,int y)
{
x=Findfa(x),y=Findfa(y);
if(x==y)
return false;
setfa[x]=y;
return true;
}
void Init()
{
for(int i=1;i<=n;i++)
setfa[i]=i;
}
void Merge(int *seq,int *seq1,int *seq2)//把seq1和seq2合并到seq中
{
static int tmp[MAXN*2+5];//归并排序进行Kruskal,以规避sort的log(卡常数)
tmp[0]=0;Init();
int i=1,j=1;
while(i<=seq1[0]||j<=seq2[0])
{
if(j>seq2[0]||(i<=seq1[0]&&edges[seq1[i]].w<edges[seq2[j]].w))
{
if(Union(edges[seq1[i]].u,edges[seq1[i]].v))
tmp[++tmp[0]]=seq1[i];
i++;
}
else
{
if(Union(edges[seq2[j]].u,edges[seq2[j]].v))
tmp[++tmp[0]]=seq2[j];
j++;
}
}
for(int i=0;i<=tmp[0];i++)
seq[i]=tmp[i];
}
void Build(int i,int l,int r)
{
if(l==r)
{
tree[i].seq[0]=1;
tree[i].seq[1]=l;
return;
}
int mid=(l+r)/2;
Build(i*2,l,mid);
Build(i*2+1,mid+1,r);
Merge(tree[i].seq,tree[i*2].seq,tree[i*2+1].seq);
}
void Query(int i,int l,int r,int ql,int qr)
{
if(qr<l||ql>r)
return;
if(ql<=l&&r<=qr)
{
Merge(ANS,ANS,tree[i].seq);
return;
}
int mid=(l+r)/2;
Query(i*2,l,mid,ql,qr);
Query(i*2+1,mid+1,r,ql,qr);
}
inline int read()
{
int ret=0;char c=0;
while(c<'0'||c>'9') c=getchar();
ret=10*ret+c-'0';
while(true){c=getchar();if(c<'0'||c>'9') break;ret=10*ret+c-'0';}
return ret;
}
inline void print(int x)
{
if(x==0) return;
print(x/10);
putchar(x%10+'0');
}
int main()
{
// freopen("highway.in","r",stdin);
// freopen("highway.out","w",stdout);
scanf("%d %d %d",&n,&m,&q);
int u,v,c;
for(int i=1;i<=m;i++)
{
u=read(),v=read(),c=read();
edges[i]=edge(u,v,c);
}
Build(1,1,m);
for(int i=1;i<=q;i++)
{
int l,r;
l=read(),r=read();
ANS[0]=0,ANSval=0;
Query(1,1,m,l,r);
for(int i=1;i<=ANS[0];i++)
ANSval+=edges[ANS[i]].w;//最后统一算答案
print(ANSval);putchar('\n');
}
return 0;
}
/*
3 5 2
1 3 2
2 3 1
2 1 6
3 1 7
2 3 7
2 5
3 4
*/