[牛客网NC15479]最短路

发布于 2022-03-01  857 次阅读


题目链接:https://ac.nowcoder.com/acm/contest/28848/A

题目大意

企鹅国中有N座城市,编号从1到N。
对于任意的两座城市i和j,企鹅们可以花费(i xor j) * C (xor表示异或)的时间从城市i走到城市j,这里C为一个给定的常数。
当然除此之外还有M条单向的快捷通道,第i条快捷通道从第Fi个城市通向第Ti个城市,走这条通道需要消耗Vi的时间。
现在来自Penguin Kingdom University的企鹅豆豆正在考虑从城市A前往城市B最少需要多少时间?

具体输入输出和数据范围请看原题。

思路

毫无疑问是考最短路的,所以想到dijkstra来处理无负边权的图。可是这里任意两个点之间都可以连通,这将会是个完全图(任意两点都连通的图),复杂度会达到惊人的O((N^2+M)logN),当务之急是减少边的数量。

不难发现,题目中i xor j的条件十分亮眼。

考虑到异或的性质,假如我要从5(101)走到 9(1001),括号内为二进制,可以5(0101) -> 1(0001) -> 9(1001)这样的开销和直接从5走到9的开销相同。步骤是先消去不需要的1,再加上需要的1,这样一来,边的条数就从N^2减少到NlogN,这多是一件美事啊。注意当起点为2的幂的时候,可能走到0这个点,所以我们在增加异或边的时候需要假设 0 这个点

代码

/* Copyright (C) 2022 ErikTse */
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <bitset>
#include <queue>
#include <utility>
#include <stack>
#include <algorithm>
#define pb push_back
#define fi first
#define se second
#define pr printf
#define sc scanf
#define gc getchar
#define int long long//注意使用%lld输出long long类型数据
using namespace std;
inline int readInt(){//快速读入int
	int s = 0,w = 1;char c = gc();
	while(c > '9' || c < '0')if(c == '-')w = -1,c = gc();
	while('0' <= c && c <= '9')s = s * 10 + c - '0',c = gc();
	return s * w;
}
inline string readString(){//快速读入string
	string s;char c = gc();
	while(c == ' ' || c == '\n' || c == '\r')c = gc();
	while(c != ' ' && c != '\n' && c != '\r')s += c,c = gc();
	return s;
}
/*-------全局变量------*/
const int maxn = 1e5 + 10;
struct Node
{
	int x, d;
	//重载运算符 
	bool operator < (const Node &m)const{return d > m.d;}
};
int N, M, C;
vector<vector<Node> > g;//邻接表, 
int d[maxn];
/*-----自定义函数------*/

inline int dijkstra(int A,int B)
{
	memset(d, 127, sizeof d);
	priority_queue<Node> pq;//小根堆,堆顶为距离最小的点 
	pq.push({A, d[A] = 0});
	while(!pq.empty())
	{
		int x = pq.top().x;
		int dist = pq.top().d;
		pq.pop();
		if(x == B)return d[B];//第一次找到的 B 一定是最小距离 
		if(dist != d[x])continue;//dist为 A->B 的距离 
		//拓展 
		for(auto i : g[x])//i为下一个节点 
			if(d[i.x] > dist + i.d)//如果 A->i 大于  A->B->i
				pq.push({i.x, d[i.x] = dist + i.d});
	}
    return 0;
}

signed main()
{
	//freopen("in.txt","r",stdin);
	N = readInt(), M = readInt(), C = readInt();
	g.resize(N + 1);
	//读入边
	while(M --)
	{
		int x = readInt(), y = readInt(), v = readInt();
		g[x].pb({y, v});
	}
	//添加异或边(二进制优化)
	for(int i = 0;i <= N; ++ i)//0 需要作为中转点 
	{
		for(int j = 1;j <= N; j *= 2)
			if((i ^ j) <= N)g[i].pb({i ^ j, j * C});
	}
	int A = readInt(), B = readInt();
	pr("%lld\n",dijkstra(A, B));
	return 0;
}