EducationalCF51 F - The Shortest Statement
問題
重み付き連結無向グラフが与えられる
またq個のクエリが与えられ、各クエリで頂点uiとvi間の最短距離を出力する
1 <= 頂点数n、辺数m、クエリ数q <= 10^5
m-n <= 20
1 <= 辺の重み <= 10^9
解法
制約より、グラフから辺をm-n+1本適切に削れば木になる
この木をT、削った辺をMとする
Tにおける2点間の距離は、適当な頂点を根とした根付き木と見なしてLCAを求めることで高速に求めることができる
(頂点a,bのLCAをx、根から頂点aへの距離をdt[a]などとすると、ab間の距離は(dt[a]-dt[x])+(dt[b]-dt[x])となる)
一方Mの各辺が繋ぐ頂点の集合をSとすると、Sに含まれる2頂点間の実際の最短距離は、"M" と "Tでの距離" という2種類の辺を貼ったグラフにワーシャルフロイド法を用いれば求められる
これらを組み合わせてクエリに答える
具体的には、まず答えをTでの距離で初期化し、Sに含まれる2頂点を経由する(∴Mに含まれる辺も使う)ときの最短距離で答えを更新していく
2頂点a,b間の最短距離は、Sに含まれる2頂点x,yを経由するとき、
(Tにおけるaからxまでの距離) + (Sだけで作ったグラフにおけるxからyまでの最短距離) + (Tにおけるyからbまでの距離)
となる
実装
大体の流れはコードに書いてあるコメントの通り
最初に木を作るのはUnionFindでやった
TLEについては、
m-n <= 20よりSの頂点数は最大42
クエリごとにこれの2乗(≒2*10^3)通りを全部試すことになるからその時点で10^5 * 2*10^3 = 2*10^8
ここにlogN(≒20)を付けると落ちる
class LCA { private: static const int MAXLOGV = 17; static const int MAXV = 1<<MAXLOGV; int root; vector<int> e[MAXV]; int par[MAXLOGV][MAXV], dep[MAXV]; public: void dfs(int v, int p, int d) { par[0][v] = p; dep[v] = d; for (int i=0; i<e[v].size(); i++) if (e[v][i]!=p) dfs(e[v][i], v, d+1); } void add(int from, int to) { e[from].push_back(to); e[to].push_back(from); } void init(int N, int ROOT) { root = ROOT; dfs(root, -1, 0); for (int k=0; k<MAXLOGV-1; k++) for (int v=0; v<N; v++) { if (par[k][v]<0) par[k+1][v] = -1; else par[k+1][v] = par[k][par[k][v]]; } } int lca(int u, int v) { if (dep[u]>dep[v]) swap(u,v); for (int k=0; k<MAXLOGV; k++) if ((dep[v]-dep[u])>>k&1) v = par[k][v]; if (u==v) return u; for(int k=MAXLOGV-1; k>=0; k--) if (par[k][u]!=par[k][v]) { u = par[k][u]; v = par[k][v]; } return par[0][u]; } }; struct UF { vector<int> p; UF(int n) : p(n, -1) {}; bool unite(int u, int v) { if ((u = find(u)) == (v = find(v))) return false; if (p[u] > p[v]) swap(u, v); p[u] += p[v]; p[v] = u; return true; } bool same(int u, int v) { return find(u) == find(v); } int find(int u) { return p[u] < 0 ? u : p[u] = find(p[u]); } int usize(int u) { return -p[find(u)]; } }; int n, m, a, b, c, q, dt[114514], ds[114514][44], d[44][44]; LCA lc; UF uf(114514); vector<pair<int,int> > e[114514]; vector<pair<pair<int,int>,int> > es; set<int> com; map<int,int> t; vector<int> r(44); void rec(int v, int p, int dis) { dt[v] = dis; each(i,e[v]) if (i.first!=p) rec(i.first, v, dis+i.second); } int dist(int x, int y) { return dt[x] + dt[y] - dt[lc.lca(x,y)]*2; } signed main() { cin >> n >> m; rep(i,m) { cin >> a >> b >> c; a--, b--; if (uf.same(a,b)) { es.push_back({{a,b},c}); com.insert(a), com.insert(b); } else { e[a].push_back({b,c}), e[b].push_back({a,c}); lc.add(a,b); uf.unite(a,b); } } // 根付き木の根(頂点0)から各点への距離を求める rec(0,-1,0); // 余った辺が繋ぐ頂点たちの番号を圧縮 lc.init(n,0); int num = 0; each(i,com) t[i] = num, r[num++] = i; // このnum個の頂点間の最短距離を求める rep(i,num) rep(j,num) if (i!=j) d[i][j] = linf; each(i,es) { a = i.first.first, b = i.first.second, c = i.second; int ta = t[a], tb = t[b]; d[ta][tb] = d[tb][ta] = c; } rep(i,num) reps(j,i+1,num) { int dis = dist(r[i],r[j]); chmin(d[i][j],dis), chmin(d[j][i],dis); } rep(k,num) rep(i,num) rep(j,num) chmin(d[i][j], d[i][k]+d[k][j]); // 各頂点とnum個の頂点間の距離を事前に求めておく(ここをクエリごとにやってるとTLE) rep(i,n) rep(j,num) ds[i][j] = dist(i,r[j]); // クエリに答える cin >> q; while (q--) { cin >> a >> b; a--, b--; int res = dist(a,b); rep(i,num) rep(j,num) if (i!=j) chmin(res, ds[a][i] + d[i][j] + ds[b][j]); cout << res << endl; } }
なっが