【洛谷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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章