题意: 给定$n$个点,求从$s$到$t$的最短路径,其中有两种走法(可以混搭):一种是走给定的m有向边($u_i$,$v_i$,$w_i$);另一种可以由任意点x到任意点y,其费用是$c$ $ imes$ ($x$ $xor$ $y$)
朴素的建法是$O$ ($n^2$ $ imes$ $m$)的.然而事实上一个从$x$$ o$ $y$权值为$w$的边是可以被其他边取代的,我们可以把$x$拆成二进制,一位一位的修改最终到达y,此时经过的权值显然也是$w$。
举个例子:
假设我们要从 $001_2$ 到 $010_2$,我们要花费 $2^0$ +$ 2^1$ 的费用; 但是,最短路有一个 优越的性质,我们可以把边拆开来,可以先从 $001_2$ 到$ 000_2$,再从$ 000_2$ 到$ 010_2$,费用是一样的。
也就是说,对于一个点$x$,我们只需要让他和$x$ $ imes$ $2^k$连边即可,这样就优化为$O$($nlogn$+$m$)了,跑一遍$dij$就好了。
代码:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 #include <queue> 6 #include <cmath> 7 using namespace std; 8 int n,m,c,st,sd,x,y,val; 9 struct node{ 10 int to,next,w; 11 }ed[5000005]; 12 int dis[5000005],vis[5000005],head[5000005],cnt; 13 inline void add(int u,int v,int w){ 14 ed[++cnt].next=head[u]; 15 ed[cnt].to=v; 16 ed[cnt].w=w; 17 head[u]=cnt; 18 } 19 inline int read(){ 20 int x = 1,a = 0; 21 char ch = getchar(); 22 while(ch < '0' || ch > '9'){ 23 if(ch == '-')x = -1; 24 ch = getchar(); 25 } 26 while(ch <= '9'&&ch >= '0'){ 27 a = a * 10 + ch - '0'; 28 ch = getchar(); 29 } 30 return x*a; 31 } 32 priority_queue<pair<int,int> > q; 33 inline void Dij(int s){ 34 q.push(make_pair(0,s));memset(vis,0,sizeof(vis));memset(dis,63,sizeof(dis));dis[s]=0; 35 while (!q.empty()){ 36 int x = q.top().second; 37 q.pop(); 38 if (vis[x]) continue; 39 vis[x]=1; 40 for (register int i = head[x];i;i=ed[i].next){ 41 int to = ed[i].to; 42 if (dis[to]>dis[x]+ed[i].w){ 43 dis[to]=dis[x]+ed[i].w; 44 q.push(make_pair(-dis[to],to)); 45 } 46 } 47 } 48 return; 49 } 50 int main(){ 51 n=read();m=read();c=read(); 52 for (int i = 1;i <= m;i++){ 53 x=read(),y=read(),val=read(); 54 add(x,y,val); 55 } 56 int lgn = floor(log2(n)) + 1; 57 n = (1 << lgn) - 1; 58 for (register int i = 1;i <= n;i++) 59 for (register int j = 0;j < lgn;j++) 60 add(i,i^(1<<j),(1<<j)*c); 61 st=read(),sd=read(); 62 Dij(st); 63 cout<<dis[sd]; 64 }