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));
}
}