P4366
题目:P4366
大致就是给你n个节点,其中有m条单向通道,而且对于任意两点i,j都能直接到达,权值为i xor j。
很显然不能两两点间直接建边,不然n*n+m的边数不MLE也得TLE。所以我们可以考虑减少一些不必要的边,然后直接正常跑Dijkstra就好了。
m条单向通道不好减,而对于一对点(i,j)(1<=i,j<=n),这里先拿3(011)和5(101)举例。
边011->101的权值为110,我们可以理解成一次性改动2位。
那如果一位一位改动呢?那就是先改110中最高位的1(此时011变为111),再改动110中第二位的1(111变为101),体现到图上就是011->111->101。所以011->101就等价为011->111->101。
或者先变动第二位的1再最高位1,就是011->001->101。
那同理,对于任意节点i,通俗的说,只需要将 i 与 和i二进制数位只相差1位的节点 v 建边就好。
这样不难发现,i肯定能经过若干次的、不同位的变换变为节点j。
※注意,如果节点v超出了n的范围就没必要建了(因为肯定能通过更小的v使i->j)。以及记得考虑0节点,因为可能出现到达节点0的情况(比如1可能变换最低位成0)
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
const int M=5e5+5;
const int inf=1e16;
int n,m,c,h[N],tot,st,en,d[N],vis[N];
struct sw{
int u,v,w,nxt;
}e[M+N*30];//建边边数就是M+NlogN
struct WS{
int num,dist;
bool operator <(const WS Ws)const{//习惯运算符重载的写法
return dist>Ws.dist;
}
};
priority_queue<WS> q;
void add(int u,int v,int w){
e[++tot]={u,v,w,h[u]};
h[u]=tot;
}
void dij(){//正常的堆优化Dijkstra,反正我的没被卡
for(int i=0;i<=n;i++){
d[i]=inf;
}
d[st]=0;
q.push((WS){st,0});
while(!q.empty()){
int s=q.top().num;q.pop();
if(vis[s]) continue;
vis[s]=1;
for(int i=h[s];i;i=e[i].nxt){
int v=e[i].v;
if(vis[v]) continue;
if(d[v]>d[s]+e[i].w){
d[v]=d[s]+e[i].w;
q.push((WS){v,d[v]});//
}
}
}
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&c);
for(int i=1;i<=m;i++){//m条快速通道
int u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
add(u,v,w);
}
for(int i=0;i<=n;i++){//要考虑节点0
for(int j=0;j<=20;j++){
if((i^(1<<j))>n){//v点太大就没必要建了
continue;
}
add(i,(i^(1<<j)),(1<<j)*c);//与v点建边
}
}
scanf("%lld%lld",&st,&en);
dij();
printf("%lld",d[en]);//输出
return 0;
}

浙公网安备 33010602011771号