题解 DTOJ #3861.基里巴斯(path)
欢迎访问 My Luogu Space。
【题目描述】
最近,帕特里克沉迷于世界地图上的太平洋地区。他发现了一个名字奇异的岛国:基里巴斯共和国,简称基里巴斯,是一个太平洋岛国。
其由 \(33\) 个岛屿组成。
“可惜它快被淹没了,该死的全球变暖”。
真悲哀。
我们这里讨论其在四维平行宇宙中的另一个国家:巴里基斯。这个国家由\(N\) 个岛屿或岛礁组成。
由于岛屿众多,政府在所有的岛屿之间均设有固定的经济航线连接。对于编号为 \(u, v\) 的岛屿 \((u, v)\) 对,连接两岛的单向航线通行费用是 \((u \oplus v) \times K\) 。其中, \(\oplus\) 表示异或\((Xor )\)。
泛美航空公司开设了 \(M\) 条往返于各岛的单向航线,每条航线均有一个固定的通行费用以期以更低的价格占有更多的用户群。
另外,泛美打算在岛礁中心建设了一个机场作为中转枢纽,这个枢纽建成后或许能大大降低泛美的航空成本。但是作为邪恶的资本主义财团,董事长还是希望调研一下收益。
于是,他打算从 \(S\) 飞到城市 \(T\) 。他希望得知从 \(S\) 到城市 \(T\) 的最小费用。
【输入输出格式】
输入格式:
第一行一个整数\(n (1<=n<=1000000)\),表示商品数量。
接下来一行有 \(n\) 个整数,表示每件商品的价格,范围在 \([1,10^9]\) 。
接下来一行有一个整数 \(m (1<=m<=1000000)\),表示询问数。
接下来 \(m\) 行,每行一个整数 \(ki\)。
输出格式:
对于每个询问,输出一行表示至少要带多少钱。若无法满足要求,输出 \(-1\) 。
【输入输出样例】
输入样例1:
5 3 3
1 3 2
2 4 4
2 3 2
1 5
输出样例1:
7
9
-1
输入样例2:
1000 1 85
829 630 1
633 492
输出样例2:
77945
【标签】
最短路。
【分析】
通过减少加边的个数来加快最短路算法。
基本想法:
对于每一个点 \(i\),向其他所有点 \(j\) 连一条费用为 \(i\) \(xor\) \(j\) 的边。再连上题目中给的边,跑单源最短路。
改进:
我们发现,对于两个点,如果两个点的异或结果包含超过一个二进制位,可以通过先走到只有一个二进制位不同的点,再走到另一个二进制位也不同的点来达到一样的效果,而且费用是相等的:费用只是单纯的依靠异或的结果来计算。
因此,对于每一个点我们只需向与它二进制有一位之差的每一个点(注意判断点的范围)连边就好。然后跑堆优化Dijkstra。
【代码 】
[C++]
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
struct Edge{
int to; LL w;
}E[20000005];
struct Point{ //堆优化
int id; LL dis;
bool operator<(const Point &T)const{
return dis > T.dis;
}
};
int n, m, s, t, ct, K;
int Hed[200005], Nex[20000005];
LL Dis[200005];
char F[200005];
void Add(int a, int b, LL w){
E[++ct].to = b, E[ct].w = w, Nex[ct] = Hed[a], Hed[a] = ct;
}
LL Dijkstra(){
priority_queue<Point> Q; Q.push((Point){s, 0ll});
memset(Dis, 0x3f, sizeof Dis);
Dis[s] = 0ll; int k;
while(!Q.empty()){
k = Q.top().id; Q.pop();
if(k == t) break; //汇点的答案一旦被确定就可以直接退出
F[k] = 1;
for(int i=Hed[k]; i; i=Nex[i]){
if(Dis[k]+E[i].w < Dis[E[i].to]){
Dis[E[i].to] = Dis[k]+E[i].w;
Q.push((Point){E[i].to, Dis[E[i].to]});
}
}
while(!Q.empty() && F[Q.top().id]) Q.pop();
}
return Dis[t];
}
int main(){
scanf("%d%d%d", &n, &m, &K);
int u, v, w;
while(m--){
scanf("%d%d%d", &u, &v, &w);
Add(u, v, w);
}
for(int i=1; i<=n ; ++i)
for(int j=1; j<=(1<<20); j<<=1)
if((i^j) <= n && (i^j)) Add(i, i^j, 1ll*j*K); //加边
scanf("%d%d", &s, &t);
printf("%lld", Dijkstra());
return 0;
}
【补充】
注意加边时候的范围,不要漏加或者多加了。