BZOJ 2125: 最短路(仙人掌 圆方树)

题面

BZOJ
给出一棵仙人掌(每条边最多属于一个环),多次询问求两点最短路。

题解

建圆方树,分\(lca\)是圆点还是方点讨论一下。具体见 神犇yyb的博客。但是题目并没有保证没有重边,而这个链接里的代码是默认没有重边(也就是没有长度为二的环)的,所以下面这组数据可以\(hack\)他。

2 2 1
1 2 100
2 1 200
1 2

显然答案是\(100\),但是上面链接中的博客出\(200\)

正确的写法是内似边双的写法。而且找返祖边必须开个\(vector\)存一下。

CODE

注意仙人掌的边数是\(2n\)级别的。具体来说\(n-1\le m\le 2n-2\)

#include <bits/stdc++.h>
using namespace std;
inline void read(int &x) {
	char ch; while(!isdigit(ch=getchar()));
	for(x=ch-'0';isdigit(ch=getchar());x=x*10+ch-'0');
}
const int MAXN = 10005;
const int MAXM = 20005;
int n, m, q;
bool flg[MAXN]; int cir[MAXM];
struct RST { //圆方树
	int fir[MAXM], to[MAXM<<1], nxt[MAXM<<1], wt[MAXM<<1], cnt;
	inline void add(int u, int v, int w) {
		to[++cnt] = v; nxt[cnt] = fir[u]; fir[u] = cnt; wt[cnt] = w;
		to[++cnt] = u; nxt[cnt] = fir[v]; fir[v] = cnt; wt[cnt] = w;
	}
	int dfn[MAXM], tmr, seq[MAXM], sz[MAXM], dep[MAXM], fa[MAXM], son[MAXM], top[MAXM], dis[MAXM];
	void dfs1(int u, int ff) {
		dep[u] = dep[fa[u]=ff] + (sz[u]=1);
		for(int i = fir[u], v; i; i = nxt[i])
			if((v=to[i]) != ff) {
				dis[v] = dis[u] + wt[i];
				dfs1(v, u); sz[u] += sz[v];
				if(sz[v] > sz[son[u]]) son[u] = v;
			}
	}
	void dfs2(int u, int tp) {
		seq[dfn[u] = ++tmr] = u; top[u] = tp;
		if(son[u]) dfs2(son[u], tp);
		for(int i = fir[u], v; i; i = nxt[i])
			if((v=to[i]) != fa[u] && v != son[u])
				dfs2(v, v);
	}
	int lca(int u, int v) {
		while(top[u] != top[v]) {
			if(dep[top[u]] > dep[top[v]]) u = fa[top[u]];
			else v = fa[top[v]];
		}
		return dep[u] < dep[v] ? u : v;
	}
	int jump(int u, int v) { //u点跳到v点的儿子处
		int re;
		while(top[u] != top[v])
			re = top[u], u = fa[top[u]];
		return u == v ? re : seq[dfn[v]+1];
	}
	int dist(int u, int v) {
		int k = lca(u, v);
		if(k <= n) return dis[u] + dis[v] - 2*dis[k];
		int a = jump(u, k), b = jump(v, k);
		int d1 = dis[a]-dis[k], d2 = dis[b]-dis[k];
		if(!flg[a]) d1 = cir[k]-d1;
		if(!flg[b]) d2 = cir[k]-d2;
		return dis[u]-dis[a] + dis[v]-dis[b] + min(abs(d1-d2), cir[k]-abs(d1-d2));
	}
	void pre() {
		dfs1(1, 0); dfs2(1, 1);
	}
}T;
int fir[MAXN], to[MAXN<<2], wt[MAXN<<2], nxt[MAXN<<2], cnt = 1;
inline void link(int u, int v, int w) {
	to[++cnt] = v; nxt[cnt] = fir[u]; fir[u] = cnt; wt[cnt] = w;
	to[++cnt] = u; nxt[cnt] = fir[v]; fir[v] = cnt; wt[cnt] = w;
}
int dfn[MAXN], low[MAXN], dep[MAXN], dis[MAXN], fa[MAXN], tmr, tot;
int seq[MAXN];
vector<pair<int,int> >vec[MAXN];
void Build(int tp, int bt, int L) {
	int top = dep[bt]-dep[tp]+1, C = L, d = 0;
	for(int i = bt; i != tp; i = fa[i]) seq[top--] = i, C += dis[i]-dis[fa[i]];
	seq[1] = tp; top = dep[bt]-dep[tp]+1; cir[++tot] = C;
	for(int i = 1; i <= top; ++i) {
		int mind = min(d, C-d);
		T.add(tot, seq[i], mind);
		flg[seq[i]] = (mind == d); //flg=0表示mind经过了返祖边,flg=1表示mind没有经过返祖边
		d += dis[seq[i+1]]-dis[seq[i]];
	}
}

void Dfs(int u, int ff) {
	dfn[u] = low[u] = ++tmr;
	for(int i = fir[u], v; i; i = nxt[i])
		if((i^1) != ff) {
			if(!dfn[v=to[i]]) {
				dep[v] = dep[fa[v]=u] + 1;
				dis[v] = dis[u] + wt[i];
				Dfs(v, i), low[u] = min(low[u], low[v]);
			}
			else {
				low[u] = min(low[u], dfn[v]);
				vec[v].push_back(pair<int,int>(u, wt[i])); //返祖边标记
			}
			if(dfn[u] < low[v]) T.add(u, v, wt[i]);
		}
	for(int i = vec[u].size()-1; i >= 0; --i)
		Build(u, vec[u][i].first, vec[u][i].second);
}

int main () {
	read(n), read(m), read(q);
	for(int i = 1, u, v, w; i <= m; ++i)
		read(u), read(v), read(w), link(u, v, w);
	tot = n; Dfs(1, 0); T.pre();
	int x, y;
	while(q--) {
		read(x), read(y);
		printf("%d\n", T.dist(x, y));
	}
}
posted @ 2020-01-06 17:25  _Ark  阅读(111)  评论(0编辑  收藏  举报