【洛谷P4949】最短距离【树剖】【基环树】

题目大意:

题目链接:https://www.luogu.org/problem/P4949
给出一个NN个点NN条边的无向连通图。
你需要支持两种操作:

  1. 修改 第xx条边的长度为yy
  2. 查询 点xx到点yy的最短距离

共有MM次操作。


思路:

第一道没有看题解写出来的黑题祭。果然还是tcltcl
这道题给出的图是一棵基环树,先考虑如果是一棵树应该如何处理。
显然一棵树的时候就是树链剖分的一道裸题。线段树单点修改和查询区间和即可。
那么在这棵树上加上一条边(u,v)(u,v),原来(x,y)(x,y)的答案可能会有以下改变:

  1. 依然是没有加边前的答案dis(x,y)dis(x,y)
  2. dis(x,u)+dis(u,v)+dis(y,v)dis(x,u)+dis(u,v)+dis(y,v)
  3. dis(x,v)+dis(u,v)+dis(y,u)dis(x,v)+dis(u,v)+dis(y,u)

而我们对于一条路径(p,q)(p,q),只需要求出p,qp,qlcalca,然后路径长度就分成(p,lca)+(q,lca)(p,lca)+(q,lca)
所以先找到基环树上的环,在环上随便删除一条边,然后每次询问就分别求出(x,y),(x,u),(x,v),(y,u),(y,v)(x,y),(x,u),(x,v),(y,u),(y,v)的长度,然后在上述三条式子中取最小值即可。
对了最开始肯定套路性的把边权转换为点权再做树剖。
时间复杂度O(mlog2n)O(m\log^2 n),常数巨大,因为每次要求5组点对的距离,而每组点对又要拆分成两条路径并求一个lcalca。。。
肯定没有黑题难度啊。


代码:

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=100010;
int head[N],son[N],fa[N],size[N],dep[N],top[N],id[N],rk[N],in[N],U[N],V[N],Dis[N],val[N];
int n,m,tot,flag,opt,x,y,ans1,ans2,ans3,LCA;
queue<int> q;

struct edge
{
	int next,to,dis;
}e[N*2];

void add(int from,int to,int dis)
{
	e[++tot].to=to;
	e[tot].dis=dis;
	e[tot].next=head[from];
	head[from]=tot;
}

struct Treenode
{
	int l,r,sum;
};

struct Tree
{
	Treenode tree[N*4];
	
	int pushup(int x)
	{
		tree[x].sum=tree[x*2].sum+tree[x*2+1].sum;
	}
	
	void build(int x,int l,int r)
	{
		tree[x].l=l; tree[x].r=r;
		if (l==r)
		{
			tree[x].sum=val[rk[l]];
			return;
		}
		int mid=(l+r)>>1;
		build(x*2,l,mid); build(x*2+1,mid+1,r);
		pushup(x);
	}
	
	void update(int x,int k,int v)
	{
		if (tree[x].l==k && tree[x].r==k)
		{
			tree[x].sum=v;
			return;
		}
		int mid=(tree[x].l+tree[x].r)>>1;
		if (k<=mid) update(x*2,k,v);
			else update(x*2+1,k,v);
		pushup(x);
	}
	
	int ask(int x,int l,int r)
	{
		if (tree[x].l==l && tree[x].r==r)
			return tree[x].sum;
		int mid=(tree[x].l+tree[x].r)>>1;
		if (r<=mid) return ask(x*2,l,r);
		if (l>mid) return ask(x*2+1,l,r);
		return ask(x*2,l,mid)+ask(x*2+1,mid+1,r); 
	}
}Tree;

void dfs1(int x,int f)
{
	dep[x]=dep[f]+1; size[x]=1; fa[x]=f;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=f)
		{
			dfs1(v,x);
			size[x]+=size[v];
			if (size[v]>size[son[x]]) son[x]=v;
		}
	}
}

void dfs2(int x,int tp)
{
	top[x]=tp; id[x]=++tot; rk[tot]=x;
	for (int i=head[x];~i;i=e[i].next)
		if (e[i].to==son[x]) dfs2(e[i].to,tp);
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=son[x] && v!=fa[x]) dfs2(v,v);
	}
}

int lca(int x,int y)
{
	while (top[x]!=top[y])
	{
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	if (dep[x]<dep[y]) return x;
		else return y;
}

int ask(int x,int y)
{
	int ans=0;
	while (top[x]!=top[y])
	{
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		ans+=Tree.ask(1,id[top[x]],id[x]);
		x=fa[top[x]];
	}
	if (dep[x]>dep[y]) swap(x,y);
	return ans+Tree.ask(1,id[x],id[y]);
}

int ask_(int x,int y)  //拆分成两条路径
{
	LCA=lca(x,y);
	return ask(x,LCA)+ask(y,LCA)-Tree.ask(1,id[LCA],id[LCA])*2;
}

void topsort()  //基环树拓扑排序求环
{
	for (int i=1;i<=n;i++)
		if (in[i]==1) q.push(i);
	while (q.size())
	{
		int u=q.front();
		q.pop();
		for (int i=head[u];~i;i=e[i].next)
		{
			int v=e[i].to;
			in[v]--;
			if (in[v]==1) q.push(v);
		}
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&U[i],&V[i],&Dis[i]);
		add(U[i],V[i],Dis[i]); add(V[i],U[i],Dis[i]);
		in[U[i]]++; in[V[i]]++;
	}
	topsort();
	tot=0;
	memset(head,-1,sizeof(head));
	for (int i=1;i<=n;i++)
		if (in[U[i]]>1 && in[V[i]]>1 && !flag)
			flag=i;  //记录环上的任意一条边
		else
			add(U[i],V[i],Dis[i]),add(V[i],U[i],Dis[i]);
	tot=0;
	dfs1(1,0); dfs2(1,1);
	for (int i=1;i<=n;i++)
		if (dep[U[i]]>dep[V[i]])
			val[U[i]]=Dis[i];
		else
			val[V[i]]=Dis[i];
	Tree.build(1,1,n);
	while (m--)
	{
		scanf("%d%d%d",&opt,&x,&y);
		if (opt==1)
		{
			if (x==flag) Dis[x]=y;
			else if (dep[U[x]]>dep[V[x]]) Tree.update(1,id[U[x]],y);
				else Tree.update(1,id[V[x]],y);
		}
		else
		{
			ans1=ask_(x,y);
			ans2=ask_(x,U[flag])+ask_(y,V[flag])+Dis[flag];
			ans3=ask_(x,V[flag])+ask_(y,U[flag])+Dis[flag];
			printf("%d\n",min(ans1,min(ans2,ans3)));
		}
	}
	return 0;
}
posted @ 2019-10-24 20:58  全OI最菜  阅读(151)  评论(0编辑  收藏  举报