BZOJ 5216 [Lydsy2017省队十连测]公路建设 线段树维护 最小生成树

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/88898453

title

BZOJ 5216
在这里插入图片描述
在这里插入图片描述

analysis

  • 30 分做法
    对于 30% 的数据, n 100 m 1000 q 1000 n ≤ 100,m ≤ 1000,q ≤ 1000
    暴力用 K r u s k a l Kruskal 算法求出最小生成森林即可。
    时间复杂度 O ( q m ( l o g m + α ( n ) ) ) O(qm(log m + α(n)))

  • 60 分做法
    对于 60% 的数据,边权随编号递增。
    注意到边权随编号递增,因此从大到小依次加入每条边。
    每加入一条边时,如果没有成环,那么它就是树边,否则要
    切掉环上最大的那条边,再连上这条边。
    那么询问 [ l , r ] [l,r] 的答案就是加入了所有 [ l , m ] [l, m] 的边后,编号不
    超过 r r 的树边的权值之和。
    对于树结构的维护可以暴力,对于权值和的询问可以用树状
    数组维护。
    时间复杂度 O ( m n + ( m + q ) l o g m ) O(mn + (m + q)log m)

  • 100 分做法
    考虑用线段树直接维护每个区间的答案。
    注意到一个区间最多只有 n 1 n − 1 条树边有用,所以线段树每
    个节点按权值从小到大保存区间内用到的树边即可。
    合并两个区间的信息时,只需要将树边归并,然后做
    K r u s k a l Kruskal 算法。
    时间复杂度 O ( ( m + q l o g m ) n α ( n ) ) O((m + q log m)nα(n))

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
struct mst
{
	int x,y,z,id;
	inline bool operator < (const mst &a) const
	{
		return z<a.z;
	}
}e[maxn],tmp[maxn];
struct sgt
{
	int pos[105];//记录地址
	int cnt;//选择边的数量
	inline void insert(int x)
	{
		pos[++cnt]=x;
	}
}tree[maxn<<2],Stack[maxn];
///////////////////////////////////////////////////////////////Here is MST
int fa[maxn],top,cnt,n,m,q;
inline int get(int x)
{
	return fa[x]==x?x:fa[x]=get(fa[x]);
}
inline sgt merge(sgt x,sgt y)
{
	sgt z;
	z.cnt=0;
	cnt=0;
	for (int i=1; i<=x.cnt; ++i)//将两个节点合并
		tmp[++cnt]=e[x.pos[i]];
	for (int i=1; i<=y.cnt; ++i)
		tmp[++cnt]=e[y.pos[i]];
	sort(tmp+1,tmp+cnt+1);//跑一遍Kruskal
	for (int i=1; i<=n; ++i)
		fa[i]=i;
	for (int i=1; i<=cnt; ++i)
	{
		int x=get(tmp[i].x),y=get(tmp[i].y);
		if (x==y) continue;
		fa[x]=y;
		z.pos[++z.cnt]=tmp[i].id;//所选取的较优秀的边,记录下他的地址
	}
	return z;
}
///////////////////////////////////////////////////////////////Above is MST
///////////////////////////////////////////////////////////////Here is SGT
inline void build(int now,int l,int r)
{
	if (l==r)
	{
		tree[now].insert(l);
		return ;
	}
	int mid=(l+r)>>1;
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	tree[now]=merge(tree[now<<1],tree[now<<1|1]);//同上
}
inline void ask(int now,int l,int r,int tl,int tr)
{
	if (tl<=l && r<=tr)
	{
		Stack[++top]=tree[now];//加入答案集合中
		return ;
	}
	int mid=(l+r)>>1;
	if (tl<=mid)
		ask(now<<1,l,mid,tl,tr);
	if (tr>mid)
		ask(now<<1|1,mid+1,r,tl,tr);
}
///////////////////////////////////////////////////////////////Above is SGT
inline int work(int l,int r)
{
	top=0;
	ask(1,1,m,l,r);
	for (int i=2; i<=top; ++i)
		Stack[1]=merge(Stack[1],Stack[i]);
	int ans=0;
	for (int i=1; i<=Stack[1].cnt; ++i)
		ans+=e[Stack[1].pos[i]].z;
	return ans;
}
int main()
{
	read(n);read(m);read(q);
	for (int i=1; i<=m; ++i)
	{
		read(e[i].x);read(e[i].y);read(e[i].z);
		e[i].id=i;
	}
	build(1,1,m);
	while (q--)
	{
		int l,r;
		read(l);read(r);
		printf("%d\n",work(l,r));
	}
	return 0;
}

summary

嗯,是真没看出来三十分暴力可以写最小生成树,哎,越发乎觉得知识点掌握的不够熟练,都已经想到了暴力删边求求树直径了,为什么不往下再想一步呢,对自己也是很失望,马上要省选了,虽然我是去打酱油的,但是也想漂亮的回来,这样子怎么行?哎,只能继续努力了。。。。

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/88898453