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;
} 
posted @ 2025-07-07 15:02  qwqSW  阅读(7)  评论(0)    收藏  举报