【学习笔记】【Luogu P5236】【模板】静态仙人掌

题目大意:

现在给出一个仙人掌图(即每条边最多只出现在一个环里),给出多个询问,每个询问求出两点的最短距离。

正文:

概述:

仙人掌是图,由于时空限制,直接求多源最短路径会超时超空,所以我们通过 圆方树 来将其转化为树上问题。

圆方树:

关于圆方树,要讲得通俗易懂,原图里每个节点都是原点,将每个环里加入一个方点,方点直接连向环内各个节点,如图:

这个建方点的操作用 Tarjan 做就行了!

inline void solve (int u, int v, int w)   //建方点 
{
	++ext;
	int minn, pre = w, i = v;
	while (i != f[u][0])
	{
		sum[i] = pre;
		pre += b[i];
		i = f[i][0];
	}
	sum[ext] = sum[u];
	sum[u] = 0;
	i = v;
	while(i != f[u][0])
	{
		minn = min(sum[i], sum[ext] - sum[i]);
		add_(ext, i, minn);
		add_(i, ext, minn);
		i = f[i][0];
	}
}

void Tarjan(int u, int fa)
{
	dfn[u] = low[u] = ++cnt;
	for (int i = head[u]; i; i = e[i].next)  //Tarjan 模板 
	{
		int v = e[i].to, w = e[i].w;
		if(v == fa) continue;
		if(!dfn[v])
		{
			f[v][0] = u;
			b[v] = w;
			dis[v] = dis[u] + w;
			Tarjan(v, u);
			low[u] = min(low[u], low[v]);
		}
		else low[u] = min(low[u], dfn[v]);
		if(low[v] <= dfn[u]) continue;    // 建圆点 
		add_(u, v, w);
		add_(v, u, w);
	}
	for (int i = head[u]; i; i = e[i].next)  //找到非树边(环),建方点 
	{
		int v = e[i].to;
		if(f[v][0] == u || dfn[v] <= dfn[u]) continue;  
		solve(u, v, e[i].w);
	}
}


对于剩下的问题——两点距离,先找到 \(u,v\) 的最近公共祖先 \(a\)\(a\) 是圆点直接求。如果是方点:

假设 \(u,v\) 父亲分别是 \(A,B\),发现如果是方点,答案就是 \(\operatorname{dis}(A,B)+\operatorname{dis}(u,A)+\operatorname{dis}(v,B)\)

ll lca (int X, int Y)
{
	int Lca, x = X, y = Y;
	if (d[x] > d[y])
	{
		int t = x;
		x = y;
		y = t;
	}
	for (int i = 20; i >= 0; i--)
		if (d[f[y][i]] >= d[x])
			y = f[y][i];
	if (x == y) Lca = x;
	else
	{
		for (int i = 20; i >= 0; i--)
			if (f[x][i] != f[y][i])
			{
				x = f[x][i];
				y = f[y][i];
			}
		Lca = f[y][0];
	}
	ll calc = dis[X] + dis[Y] - (dis[Lca] << 1);
	if(Lca > n)
	{
		calc -= (dis[x] - dis[Lca]) + (dis[y] - dis[Lca]);
		calc += min(abs(sum[y] - sum[x]), sum[Lca] - abs(sum[y] - sum[x]));
	}
	return calc;
}

全部代码:

初始图和圆方树记得分着存。


struct edge
{
	int from, to, next, w;
}e[M], ne[M];
int head[N], h[N], tot, total;

void add(int u, int v, int w)
{
	e[++tot] = (edge){u, v, head[u], w}, head[u] = tot;
}
void add_(int u, int v, int w)
{
	ne[++total] = (edge){u, v, h[u], w}, h[u] = total;
}

int dfn[N], low[N], f[N][22], cnt, d[N], b[N]; //b[u]表示u到父节点的价值 
ll dis[N], sum[N];
inline void solve (int u, int v, int w)   //建方点 
{
	++ext;
	int minn, pre = w, i = v;
	while (i != f[u][0])
	{
		sum[i] = pre;
		pre += b[i];
		i = f[i][0];
	}
	sum[ext] = sum[u];
	sum[u] = 0;
	i = v;
	while(i != f[u][0])
	{
		minn = min(sum[i], sum[ext] - sum[i]);
		add_(ext, i, minn);
		add_(i, ext, minn);
		i = f[i][0];
	}
}

void Tarjan(int u, int fa)
{
	dfn[u] = low[u] = ++cnt;
	for (int i = head[u]; i; i = e[i].next)  //Tarjan 模板 
	{
		int v = e[i].to, w = e[i].w;
		if(v == fa) continue;
		if(!dfn[v])
		{
			f[v][0] = u;
			b[v] = w;
			dis[v] = dis[u] + w;
			Tarjan(v, u);
			low[u] = min(low[u], low[v]);
		}
		else low[u] = min(low[u], dfn[v]);
		if(low[v] <= dfn[u]) continue;    // 建圆点 
		add_(u, v, w);
		add_(v, u, w);
	}
	for (int i = head[u]; i; i = e[i].next)  //找到非树边(环),建方点 
	{
		int v = e[i].to;
		if(f[v][0] == u || dfn[v] <= dfn[u]) continue;  
		solve(u, v, e[i].w);
	}
}

queue<int> que;

void dfs (int x, int fa)
{
	d[x] = d[fa] + 1;
	f[x][0] = fa; 
	for (int i = h[x]; i; i = ne[i].next)
	{
		int y = ne[i].to;
		if(y == fa) continue;
		dis[y] = dis[x] + ne[i].w;
		dfs (y, x);
	}
	 
} 

ll lca (int X, int Y)
{
	int Lca, x = X, y = Y;
	if (d[x] > d[y])
	{
		int t = x;
		x = y;
		y = t;
	}
	for (int i = 20; i >= 0; i--)
		if (d[f[y][i]] >= d[x])
			y = f[y][i];
	if (x == y) Lca = x;
	else
	{
		for (int i = 20; i >= 0; i--)
			if (f[x][i] != f[y][i])
			{
				x = f[x][i];
				y = f[y][i];
			}
		Lca = f[y][0];
	}
	ll calc = dis[X] + dis[Y] - (dis[Lca] << 1);
	if(Lca > n)
	{
		calc -= (dis[x] - dis[Lca]) + (dis[y] - dis[Lca]);
		calc += min(abs(sum[y] - sum[x]), sum[Lca] - abs(sum[y] - sum[x]));
	}
	return calc;
}

int main()
{
	scanf ("%d%d", &n, &m);
	ext = n;
	for (int i = 1; i <= m; ++i)
	{
		int u, v, w;
		scanf ("%d%d%d", &u, &v, &w);
		add(u, v, w);
		add(v, u, w);
	}
	f[1][0] = 0;
	Tarjan(1, 0);
	for (int i = 1; i <= ext; i++)
		d[i] = 0, dis[i] = 0;
	dfs(1, 0);
	for (int j = 1; j <= 20; j++)
		for (int i = 1; i <= ext; i++)
			f[i][j] = f[f[i][j - 1]][j - 1];
	scanf ("%d", &q);
	for (int i = 1; i <= q; ++i)
	{
		int x, y;
		scanf ("%d%d", &x, &y);
		printf("%lld\n", lca(x, y));
	}
	return 0;
}

posted @ 2020-08-18 21:14  Jayun  阅读(124)  评论(0编辑  收藏  举报