有的最短路题目中,可能边的数目很大,朴素建图边的数目为 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。这样很难直接应用 Dijkstra 算法等。
对付这种图,一般采用的策略是:
题意:有 n n n 个点, i i i 号点和 j j j 号点之间无向边的边权为 i xor j i \operatorname{xor} j ixorj,求 1 1 1 号点到 n n n 号点的最短路。(原题:HDU 6713)
由于 i xor j xor j xor k = i xor k ≤ i xor j + j xor k i\operatorname{xor}j\operatorname{xor}j\operatorname{xor}k = i\operatorname{xor}k \le i \operatorname{xor} j + j\operatorname{xor}k ixorjxorjxork=ixork≤ixorj+jxork,因此直接从 i i i 到 k k k 一定比 i i i 到 j j j 再到 k k k 更优。所以答案就是 1 xor n 1 \operatorname{xor} n 1xorn。
题意:给定一张边带权有向图,除了已有的边外,两个点之间还可以走异或边,权重为一个常数乘以两点编号的异或值。求指定两点的最短路。(原题:LOJ 6354)
由于两个点之间的异或可以看作是一个点删掉某几位的 1、加上某几位的 1 得到,因此可以按位这种简单的模式建边。然后跑最短路即可。
priority_queue<pair<int, int> > pq;
int n, m, C, S, T;
int to[2200005], at[100005] = {0}, cnt = 0, nxt[2200005], w[2200005];
int dis[100005];
void init(){
n = read(), m = read(), C = read();
for (int i = 1; i <= m; ++i){
int u = read(), v = read(), ww = read();
w[++cnt] = ww;
to[cnt] = v, nxt[cnt] = at[u], at[u] = cnt;
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; j <<= 1)
if ((i ^ j) <= n && (i ^ j) > 0)
w[++cnt] = C * j,
to[cnt] = (i ^ j), nxt[cnt] = at[i], at[i] = cnt;
memset(dis, 0x3f, sizeof(dis));
S = read(), T = read();
}
void solve(){
dis[S] = 0;
pq.push(make_pair(0, S));
for (int i = 1; i <= n; ++i){
while (!pq.empty()){
if (dis[pq.top().second] < -pq.top().first)
pq.pop();
else break;
}
int minp = pq.top().second, dd = -pq.top().first;
pq.pop();
for (int i = at[minp]; i; i = nxt[i]){
int v = to[i];
int ww = min(w[i], C * (minp ^ v));
if (dis[v] > dd + ww)
dis[v] = dd + ww, pq.push(make_pair(-dis[v], v));
}
}
printf("%d\n", dis[T]);
}
题意:给定 n n n 个平面直角坐标系上的点,两个点之间的距离定义为横坐标之差的绝对值、纵坐标之差的绝对值,二者取较小,即 min ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ ) \min(| x_1-x_2|, |y_1-y_2|) min(∣x1−x2∣,∣y1−y2∣)。求 1 1 1 号点到 n n n 号点的最短路。(原题:BZOJ 4152)
一般这种题都要考虑点和(在某种量度下)周围点之间的关系,比方说只和周围的点建边。
事实上确实如此。我们只需要将一个点和在横坐标意义上离得最近的 2 个点以及纵坐标意义上离得最近的 2 个点连边即可。原因在于:设当前点为 i i i,对于其他点 j j j,直接从 i i i 到 j j j 不会比从 i i i 到这 4 个邻居中的一个再到 j j j 更短。
对横坐标来说,前者是一个 ∣ x i − x j ∣ |x_i-x_j| ∣xi−xj∣ 的形式,后者是 ∣ x i − x k ∣ + min ( ∣ x k − x j ∣ , ∣ y k − y j ∣ ) |x_i - x_k| + \min(|x_k - x_j|, |y_k - y_j|) ∣xi−xk∣+min(∣xk−xj∣,∣yk−yj∣) 的形式, k k k 是邻居之一。显然后者的优化空间更大。注意,这里可以通过选取恰当的邻居使得绝对值不等式取等号。
然后跑堆优化 Dijkstra 即可。
题意:给定 n n n 个点,第 i i i 个点有点权 a i a_i ai。如果对于 i , j i, j i,j 有 a i and a j a_i \operatorname{and} a_j aiandaj 不为 0 0 0,那么 i , j i, j i,j 间有无向边,边权为 lowbit ( a i and a j ) \operatorname{lowbit}(a_i \operatorname{and} a_j) lowbit(aiandaj)。问从 1 1 1 到 n n n 的最短路。(原题:牛客练习赛 67 E)
我们可以依次考虑每一个位,然后把点权有相同位的点互相连一条边。但这样边的数目可能是 O ( n 2 ) O(n^2) O(n2)。
更好的做法是对当前位建立一个虚拟点,设为 u u u。遍历所有点,如果 i i i 的点权满足这一位上为 1 1 1,那么连一条从 i i i 到 u u u 的边,边权为这一位对应的二进制数;再连一条从 u u u 到 i i i 的边,边权为 0 0 0。这样边数就下降到了至多 64 n 64n 64n。跑 Dijkstra 可以通过。
int n;
unsigned a[100005];
ll dis[100005 + 50];
priority_queue<pair<int, ll>, vector<pair<int, ll> >,
greater<pair<int, ll> > > pq;
int to[6400005], nxt[6400006], at[100005 + 50], cnt;
unsigned w[6400005];
void init(){
n = read();
for (int i = 1; i <= n; ++i)
scanf("%u", &a[i]);
// build graph
memset(at, 0, sizeof(at));
cnt = 0;
int nn = n;
for (unsigned t = 1; t > 0; t <<= 1){
++n;
for (int i = 1; i <= nn; ++i) {
if (a[i] & t){
to[++cnt] = n, nxt[cnt] = at[i], w[cnt] = t, at[i] = cnt;
to[++cnt] = i, nxt[cnt] = at[n], w[cnt] = 0, at[n] = cnt;
}
}
}
}
void solve(){
memset(dis, 0x3f, sizeof(dis));
ll lim = dis[1];
dis[1] = 0;
pq.push(make_pair(1, 0));
for (; ; ){
while (!pq.empty()){
if (pq.top().second > dis[pq.top().first])
pq.pop();
else break;
}
if (pq.empty()) break;
int h = pq.top().first;
ll dd = pq.top().second;
pq.pop();
for (int i = at[h]; i; i = nxt[i]){
if (dd + w[i] < dis[to[i]])
dis[to[i]] = dd + w[i],
pq.push(make_pair(to[i], dis[to[i]]));
}
}
if (dis[n - 32] == lim){
printf("Impossible\n");
}else printf("%lld\n", dis[n - 32]);
}
本题标程给的做法是:先设定一个阈值 B = 256 B = 256 B=256,将每一个 a i a_i ai 拆成多个长为 log B \log B logB 的位域,试图将每条边分到不同的位域中。
对于每一个位域,我们构建 2 B 2B 2B 个虚拟点,分为 B B B 个“起点”和 B B B 个“终点”。我们枚举 0 ≤ u , v < B 0 \le u, v < B 0≤u,v<B,如果 u and v ≠ 0 u \operatorname{and} v \neq 0 uandv=0,那么每一个位域内,都连一条边,从编号为 u u u 的“起点”到编号为 v v v 的“终点”,边权为 lowbit ( u and v ) × 2 r \operatorname{lowbit}(u \operatorname{and} v) \times 2^r lowbit(uandv)×2r,其中 2 r 2^r 2r 表示这个位域相对于最低位的偏移量。这部分建立的边数至多为 B 2 log B B^2 \log B B2logB。
然后考虑每一个原图的点 i i i,对于 a i a_i ai 的每一个位域,将 i i i 连向该位域的“起点”,而将该位域的“终点”连向 i i i。
这么说非常抽象,可以参考出题人给的题解。