【BZOJ 2238】Mst

【题目】

题目描述:

给出一个 n n n 个点 m m m 条边的无向带权图,以及 q q q 个询问,每次询问在图中删掉一条边后图的最小生成树。(各询问间独立,每次询问不会对之后的询问产生影响,即被删掉的边在下一条询问中依然存在)

输入格式:

第一行两个正整数 n , m ( n ≤ 50000 , m ≤ 100000 ) n,m(n≤50000,m≤100000) n,m(n50000,m100000) 表示原图的顶点数和边数。

下面 m m m 行,每行三个整数 x , y , w x,y,w x,y,w 描述了图的一条边 ( x , y ) (x,y) (x,y),其边权为 w ( w ≤ 10000 ) w(w≤10000) w(w10000)。保证两点之间至多只有一条边。

接着一行一个正整数 q q q,表示询问数。 ( 1 ≤ q ≤ 100000 ) (1≤q≤100000) (1q100000)

下面 q q q 行,每行一个询问,询问中包含一个正整数 t t t,表示把编号为 t t t 的边删掉(边从 1 1 1 m m m 按输入顺序编号)。

输出格式:

q q q 行,对于每个询问输出对应最小生成树的边权和的值,如果图不连通则输出 “Not connected”

样例数据:

输入
4 4
1 2 3
1 3 5
2 3 9
2 4 1
4
1
2
3
4

输出
15
13
9
Not connected

数据规模:

10 % 10\% 10% 的数据 n , m , q ≤ 100 n,m,q\le 100 n,m,q100
另外 30 % 30\% 30% 的数据, n ≤ 1000 n\le 1000 n1000
100 % 100\% 100% 的数据如题目。


【分析】

一道比较妙的题

首先用 k r u s k a l kruskal kruskal 把整张图的最小生成树求出来

如果要删掉的边是非树边,那答案就是最小生成树的值,现在考虑删掉树边

对于一条树边,考虑哪些非树边可以"代替"它(也就是删掉这条树边,加上非树边后依旧是一颗生成树)

不难发现的是,只有这条树边在非树边两端点的简单路径上,那才可以被替代

所以我们就从能把它替代的所有非树边中找出最短的一条,那此时的最小生成树自然是最小的

如果用 Min[i] 表示能替代 i i i 这条边的最短非树边的话,那么就枚举每条非树边,对于这条非树边两端点的简单路径上的所有树边取个 min(Min[i],w[i]),w[i] 是边权(就是预处理出 Min[i]

查询的时候 ans=MST-w[x]+Min[x]MST 是最小生成树的值

因此用树链剖分维护链修改,用线段树维护区间取 m i n min min

然后,Not connected 有两种情况:

  1. 在不删边的情况下就已经不连通,全部输出 Not connected
  2. 没有发现能替代当前树边的非树边,输出 Not connected

注意转边为点


【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 50005
#define M 200005
#define inf 0x3f3f3f3f
using namespace std;
int n,m,q,t,tot;
int first[N],v[M],nxt[M];
int father[N],ID[M],Min[N<<2],mark[N<<2];
int fa[N],dep[N],son[N],top[N],pos[N],Size[N];
bool tree[M];
struct edge{int u,v,w,id;}e[M];
bool comp1(const edge &p,const edge &q){return p.w<q.w;}
bool comp2(const edge &p,const edge &q){return p.id<q.id;}
int find(int x)
{
	if(father[x]==x)  return x;
	return father[x]=find(father[x]);
}
void add(int x,int y)
{
	nxt[++t]=first[x];
	first[x]=t,v[t]=y;
}
int Kruskal()
{
	int x,y,i,ans=0,tot=0;
	sort(e+1,e+m+1,comp1);
	for(i=1;i<=n;++i)  father[i]=i;
	for(i=1;i<=m;++i)
	{
		x=find(e[i].u);
		y=find(e[i].v);
		if(x!=y)
		{
			father[y]=x;
			tot++,ans+=e[i].w;
			tree[e[i].id]=true;
			add(e[i].u,e[i].v),add(e[i].v,e[i].u);
		}
	}
	sort(e+1,e+m+1,comp2);
	return (tot==n-1)?ans:-1;
}
void dfs1(int x)
{
	int i,k;
	Size[x]=1;
	for(i=first[x];i;i=nxt[i])
	{
		if((k=v[i])==fa[x])  continue;
		fa[k]=x,dep[k]=dep[x]+1,dfs1(k),Size[x]+=Size[k];
		if(Size[son[x]]<Size[k])  son[x]=k;
	}
}
void dfs2(int x,int tp)
{
	int i;
	top[x]=tp,pos[x]=++tot;
	if(son[x])  dfs2(son[x],top[x]);
	for(i=first[x];i;i=nxt[i])
	  if(v[i]!=son[x]&&v[i]!=fa[x])
	    dfs2(v[i],v[i]);
}
void pushnow(int root,int z)
{
	Min[root]=min(Min[root],z);
	mark[root]=min(mark[root],z);
}
void pushdown(int root)
{
	if(mark[root]==inf)  return;
	pushnow(root<<1,mark[root]),pushnow(root<<1|1,mark[root]);
	mark[root]=0x3f3f3f3f;
}
void modify(int root,int l,int r,int x,int y,int z)
{
	if(l>=x&&r<=y)
	{
		pushnow(root,z);
		return;
	}
	pushdown(root);
	int mid=(l+r)>>1;
	if(x<=mid)  modify(root<<1,l,mid,x,y,z);
	if(y>mid)  modify(root<<1|1,mid+1,r,x,y,z);
	Min[root]=min(Min[root<<1],Min[root<<1|1]);
}
void change(int x,int y,int z)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])  swap(x,y);
		modify(1,1,n,pos[top[x]],pos[x],z),x=fa[top[x]];
	}
	if(dep[x]>dep[y])  swap(x,y);
	modify(1,1,n,pos[x]+1,pos[y],z);
}
int query(int root,int l,int r,int x)
{
	if(l==r)  return Min[root];
	pushdown(root);int ans=inf,mid=(l+r)>>1;
	if(x<=mid)  ans=min(ans,query(root<<1,l,mid,x));
	else  ans=min(ans,query(root<<1|1,mid+1,r,x));
	return ans;
}
int main()
{
	int x,y,z,i;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;++i)
	  scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w),e[i].id=i;
	scanf("%d",&q);
	int MST=Kruskal();
	dfs1(1),dfs2(1,1);
	memset(Min,0x3f,sizeof(Min));
	memset(mark,0x3f,sizeof(mark));
	for(i=1;i<=m;++i)
	  if(!tree[i])
	    change(e[i].u,e[i].v,e[i].w);
	for(i=1;i<=q;++i)
	{
		scanf("%d",&x);
		if(MST==-1)  puts("Not connected");
		else  if(!tree[x])  printf("%d\n",MST);
		else
		{
			int ans=query(1,1,n,max(pos[e[x].u],pos[e[x].v]));
			if(ans==inf)  puts("Not connected");
			else  printf("%d\n",MST-e[x].w+ans);
		}
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值