求点1 到点n 所有路径中 最大值与最小值之差 最小的那个

本文介绍了一个使用最小生成树算法寻找连接图中两点间最短路径的C++实现。该算法通过遍历所有边并按权重排序,利用并查集结构避免形成环路,最终找出代价最小的生成树。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MIN(x,y) ((x)>(y)?(y):(x))
using namespace std;
int pre[1000];
struct node
{
	int u,v,w;
}e[1600];
bool cmp(const node &a,const node &b)
{
	return a.w<b.w;
}
int find(int x)
{
	return x==pre[x]?x:pre[x]=find(pre[x]);
}
void unite(int x,int y)
{
	int t1=find(x);
	int t2=find(y);
	pre[t1]=t2;
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++)
	scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
	sort(e,e+m,cmp);
	int res=10000000;
	for(int i=0;i<m;i++)
	{
		for(int j=0;j<=n;j++)
		pre[j]=j;
		for(int j=i;j<m;j++)
		{
			unite(e[j].u,e[j].v);
			if(find(1)==find(n))
			{
				res=MIN(res,e[j].w-e[i].w);
				break; 
			}
		}
	}
	printf("%d\n",res);
}

<think>题目要:给定一个长度为n的数列a,如果两个下标i,j满足a[i] & a[j] != 0(按位不为0),则在这两个之间有一条长度为a[i]+a[j]的无向边。1(下标1)出发到所有的最短路,如果不能到达则输出-1。 注意:下标从1开始。 观察数据范围:n最大为10^5,a[i]在[0,2^30]之间。 如果直接枚举所有对,判断是否有边并建图,那么边的数量可能达到O(n^2),显然不可行。因此需要优化建图。 思路:利用位运算的性质。两个数按位不为0,说明存在至少一个二进制位k,使得两个数在该位上都为1。 因此,我们可以考虑对每个二进制位建立一个超级源(或者理解为一种分组),对于每个数a[i],如果它在第k位为1,那么它就和代表第k位的超级源相连。但是,这样会引入新的,而且边的权值如何定义? 重新思考:我们想要避免O(n^2)的建边,那么可以考虑将每个数a[i]它所有的二进制位代表的集合(也就是该数所有为1的位)连接。但是,这样建图后,两个数如果共享至少一个二进制位,那么它们就会通过该二进制位对应的超级节相连。也就是说,原来两个数直接有边,现在变成了通过一个中间节(超级节)的两条边。 但是,这样边的权值发生了变化:原图中两个i,j之间的边权是a[i]+a[j],而通过超级节k连接时,边权变成了(a[i]+a[k]) + (a[k]+a[j]),其中a[k]并不存在,因为超级节是我们引入的虚拟节。 因此,我们需要重新设计边的权值。我们可以这样定义: - 对于每个二进制位k(0到30),我们创建一个超级节,编号为k+n(节编号从1到n,所以超级节从n+1开始)。 - 对于每个原始节i(对应a[i]),如果a[i]的第k位为1,那么我们在节i和超级节k+n之间建立一条无向边,边权为a[i](注意,这里边权不是a[i]+a[k],因为超级节没有值,我们只考虑原始节的值,所以边权设为a[i])。这样,两个原始节i和j如果都连接了同一个超级节k,那么从i到j的路径就是i->k+n->j,总边权为a[i] + a[j]。 这样,原来i和j直接有边(边权为a[i]+a[j])的情况,现在变成了通过超级节的一条路径,且总边权相同。因此,我们可以用这种建图方式代替原来的直接建边。 但是,这样建图后,图中可能包含一些原来没有的边:比如,两个数可能没有共同的二进制位,但是通过不同的超级节路径相连。然而,题目要是当且仅当两个数有公共二进制位(即按位不为0)时才有边。而通过超级节连接的两个原始节,它们一定至少共享一个二进制位(因为都连接了同一个超级节k,说明它们都有第k位为1)。所以,这样建图后,两个原始节之间有路径当且仅当它们至少有一个公共二进制位,而且路径的边权和等于原来的直接边权(即a[i]+a[j])。另外,通过超级节还可能形成更长的路径,但最短路算法会自动选择最短的路径,所以如果两个节有公共二进制位,那么最短路就是a[i]+a[j](因为直接通过超级节路径权值就是a[i]+a[j],而如果通过多个超级节,权值会更大,因为每次经过超级节都会加上两个原始节的值,而直接路径只加了一次两个原始节的值?这里需要仔细分析)。 实际上,我们这样建图后,任意两个有公共二进制位的节i和j,它们之间有一条路径:i->k+n->j,边权为a[i]+a[j]。同时,可能还有其他路径,比如i->k+n->l+n->j,但这样的路径边权为a[i] + a[j] + a[k](超级节本身没有值,但边权是连接原始节和超级节的边权,这里a[k]并不是一个值,而是我们设定的边权为原始节的值?不对,我们设定的边权是:从原始节i到超级节k+n的边权为a[i],从超级节k+n到原始节j的边权为a[j]。所以路径i->k+n->j的边权为a[i]+a[j]。而路径i->k+n->l+n->j的边权为a[i] + a[k](这里a[k]是超级节k+n连向原始节时,原始节的值?不对,超级节k+n连向原始节j时,边权是a[j]?这里我们设定的边权都是原始节的值,但是注意,同一个原始节连接多个超级节时,边权都是该原始节的值。所以路径i->k+n->l+n->j的边权为:a[i](从i到k+n) + a[l](从k+n到l+n?不对,超级节之间没有边!)注意,我们只建立了原始节和超级节之间的边,超级节之间没有边。所以路径只能是原始节->超级节->原始节。因此,两个原始节之间只能通过一个超级节相连,路径长度就是a[i]+a[j]。不可能通过两个超级节,因为超级节之间没有边。 但是,还有一种情况:同一个原始节可以通过不同的超级节连接到其他原始节。例如,节i连接了超级节k和l,节j连接了超级节l和m,那么i和j可以通过超级节l相连(路径i->l+n->j,边权a[i]+a[j]),也可以通过超级节k和m?但这样就需要经过两个超级节,而超级节之间没有边,所以不行。因此,两个原始节之间最多只有一条边(通过一个公共超级节),且边权为a[i]+a[j]。所以,这样建图后,原始节之间的直接边被拆成了两条边,但总边权不变,并且没有增加多余的边(因为超级节之间没有边,所以路径长度不会减少?)。 然而,这里有一个问题:在原始图中,两个节之间可能有多条边(如果它们有多个公共二进制位),但在我们建的图中,它们之间仍然只有一条路径(通过任何一个公共超级节路径长度都是a[i]+a[j]),所以实际上我们只保留了一条边。但最短路算法中,我们只需要知道两之间的最小边权,而这里所有公共二进制位对应的路径边权都是a[i]+a[j],所以保留一条即可。 但是,这样建图后,图中的节数变为n+31(最多31个超级节,因为a[i]<=2^30,所以二进制位从0到30共31位),边数:每个原始节最多连31条边(因为一个数的二进制最多31位为1),所以总边数最多31*n,即310万条边,在可接受范围内。 然后,我们在这个新图上运行最短路算法(Dijkstra),起是节1(原始节1)。 注意:超级节只是中间节,我们最终要的是原始节1到其他原始节的最短路。 另外,注意1到点1的最短路是0。 但是,我们这样建图后,1到点1路径可能是什么?1连接了一些超级节,然后通过超级节再回到点1?这样会形成一条边权为a[1]+a[1]=2*a[1]的路径。但显然,1到点1的最短路应该是0。所以我们需要在算法开始时将起1的距离设为0,然后算法运行中,0会是最小值,所以不会更新自己。 另外,原始图中,两个原始节之间可能没有边(没有公共二进制位),那么在新图中,这两个原始节之间就没有路径(因为必须通过公共超级节),所以无法到达。 因此,算法步骤: 1. 创建图:节总数n+31(或者32,为了保险,我们可以创建32个超级节,编号从n+1到n+31,对应位0到30)。注意:位0对应最低位,位30对应最高位。 2. 对于每个原始节i(1<=i<=n),对于每个二进制位k(0<=k<=30): 如果a[i]的第k位为1,则添加两条边(无向边): 从i到超级节n+k+1(因为超级节编号从n+1开始),边权为a[i]。 注意:无向边就是双向边,所以添加边(i, n+k+1, a[i])和(n+k+1, i, a[i])。 3. 初始化距离数组dis,长度为n+32(节编号从1到n+31),初始化为一个很大的数(比如1e18)。将dis[1]设为0。 4. 使用优先队列(小根堆)的Dijkstra算法: 将节1加入优先队列(距离为0)。 每次取出距离最小的节u,然后遍历u的所有邻接边(u,v,weight): 如果dis[v] > dis[u] + weight,则更新dis[v] = dis[u] + weight,并将v加入优先队列。 5. 最后,对于每个原始节i(1<=i<=n),输出dis[i];如果dis[i]还是初始值(即未被更新),则输出-1。 但是,这里有一个问题:在原始问题中,从1到点i的最短路,在原始图中可能经过多个中间节。而在新图中,我们通过超级节来表示原始节之间的直接边。那么,新图中1到点i的最短路是否对应原始图中的最短路? 考虑原始图中的一条路径1->j->i。在原始图中,这条路径的权值为(a[1]+a[j]) + (a[j]+a[i]) = a[1] + 2*a[j] + a[i]。 在新图中,路径可能是:1->超级节k->j->超级节l->i。边权为:a[1](1到超级节k) + a[j](超级节k到j) + a[j](j到超级节l) + a[i](超级节l到i) = a[1] + a[j] + a[j] + a[i] = a[1] + 2*a[j] + a[i],原始图相同。 因此,新图完全模拟了原始图的边权。而且,新图中原始节之间没有直接边,只能通过超级节连接。所以,新图可以正确模拟原始图。 但是,我们还需要注意:原始图中,同一个原始节出发再回到同一个原始节是不允许的(除非有自环,但题目中自环的条件是a[i]&a[i]!=0,即a[i]!=0,所以当a[i]!=0时,自环存在,边权为2*a[i])。在新图中,自环表现为:i->超级节k->i,边权为a[i]+a[i]=2*a[i]。所以自环也被正确表示。 因此,我们可以用上述方法建图,然后跑Dijkstra。 但是,我们还需要考虑a[i]为0的情况。如果a[i]为0,那么它和任何数按位都是0,所以它没有边连向其他节(包括自己,因为0&0=0,所以也没有自环)。因此,如果a[i]=0,那么它在新图中不会连接任何超级节(因为它的每一位都是0)。所以,这样的除了起(如果是起1,则距离为0)外,其他都无法到达(除非起本身,但起1的a[1]如果是0,那么它也没有边连出去,所以除了自己其他都无法到达?但题目要1到点1的最短路是0,所以起1不管a[1]是多少,我们都要设置dis[1]=0)。 另外,注意:如果起1的a[1]为0,那么它没有边连出去,所以它只能到达自己。 因此,算法需要处理a[i]=0的情况:这样的不会连向超级节,所以在新图中是孤立的(除了起1)。 最后,注意:超级节之间没有边,所以不会增加额外的路径。 实现细节: - 二进制位枚举:从0到30,共31位。 - 超级节编号:我们设超级节编号为n+1+k(k从0到30),这样一共有31个超级节,编号范围是n+1到n+31。 - 建图:使用邻接表,每个节(包括超级节)存储邻接表(节和边权)。 - 注意:节总数是n+31,数组大小要开够。 时间复杂度:O((n+31) * log(n+31)),因为边数是31*n(约310万),节数n+31(约10万),所以Dijkstra的时间复杂度是O((n+m)log(n)),这里m=310万,n=10万,log(n)约为17,所以总操作次数约为(10万+310万)*17≈5400万,在C++中可能勉强通过(1秒),但题目时间限制1秒,需要优化常数。 另一种思路:不显式建图,而是利用位运算的性质进行优化。但是,本题已经优化了建图(边数从O(n^2)降到O(n)),所以应该可以接受。 但是,我们也可以尝试不建超级节,而是用另一种思路:-超级节的结构已经将边数降到了O(n*位数),所以可以接受。 另外,在Dijkstra算法中,我们可能会更新到超级节,然后从超级节再更新其他原始节。这样,每个超级节可能会被多次访问,但由于优先队列的性质,每个节只会被扩展一次(如果使用标记数组,确保每个节只从优先队列中取出一次)。 因此,代码步骤: 1. 读入n和数组a(大小为n+1,a[1]到a[n])。 2. 创建邻接表:大小为n+32(节编号1到n+31)。 3. 对于每个原始节i(1~n): 如果a[i]为0,则跳过(不连边)。 否则,枚举二进制位k(0~30): 如果a[i]的第k位为1: 添加边:i -> (n+1+k),权值为a[i] 添加边:n+1+k -> i,权值为a[i] (无向边,所以双向) 4. Dijkstra: dis数组,大小为n+32,初始化为一个很大的数(比如1e18,因为最坏情况,边权最大2^30,路径最多10^5条边,总权值最大10^5*2^31,约2e15,所以1e18够用)。 dis[1]=0。 优先队列(小根堆):存储(d, u),表示节u的当前距离为d。 从节1开始,更新所有邻居。 5. 最后,对于每个原始节i(1<=i<=n): 如果dis[i] >= INF(一个很大的数),则输出-1,否则输出dis[i]。 注意:超级节的距离我们不需要输出。 但是,我们更新超级节是为了通过它们更新其他原始节。 另外,注意:同一个原始节可能被多次更新,但优先队列会处理。 但是,这里有一个问题:从原始节到超级节的边权为a[i],而从超级节到原始节的边权也是a[i](同一个原始节i)。这样,当我们从原始节i到达超级节k时,距离为dis[i]+a[i],然后从超级节k到达原始节j时,距离为dis[i]+a[i]+a[j]。而原始节i和j之间通过超级节k的路径总权值为a[i]+a[j],所以从i到j的路径权值就是dis[i] + a[i] + a[j]。但是,在Dijkstra中,我们是从起1开始扩展的,所以这个dis[i]是起1到i的距离。 因此,整个路径是:起1->...->i->超级节k->j。所以,从起1到j的距离等于起1到i的距离加上a[i]+a[j]。 但是,我们也可以从起1直接通过超级节到达j:如果起1和j有公共二进制位,那么路径1->超级节->j,权值为a[1]+a[j]。所以,起1到j的距离可能由多条路径更新。 因此,算法会自动选择最小路径。 下面用样例验证: 输入:5 1 5 6 7 8 1:a[1]=1 2:a[2]=5 -> 二进制101 3:a[3]=6 -> 二进制110 4:a[4]=7 -> 二进制111 5:a[5]=8 -> 二进制1000 根据题目,1到其他的最短路: 1->1: 0 1->2: 1+5=6(因为1和5的二进制:1(001)和5(101)有公共最低位,所以有边) 1->3: 1和6(001110)没有公共位?不对,1的二进制是001,6的二进制是110,按位为0,所以没有直接边。 但是,1可以通过2到达3:1->2->3:1+5 + 5+6 = 17 1->4: 1和7(001111)有公共最低位,所以有边:1+7=8 1->5: 1和8(0011000)没有公共位,而且5和其他(除了4?): 5(8)和4(7):8(1000)和7(0111)按位为0,所以没有边;5和3:8(1000)和6(0110)按位0;5和2:8和5(1000和0101)按位0。所以5不可达。 输出:0 6 17 8 -1 建图: 11 -> 二进制:第0位为1(最低位),所以连接超级节n+1(即6,因为n=5,所以超级节编号6,7,...,36,但只需要0~30位,所以超级节编号6到36?不对,我们只需要31个超级节,编号6到36?实际上,我们只需要0~30位,所以超级节编号为5+1+k(k从0到30),即6到36。但实际我们只需要5个节?不对,k从0到30,所以超级节编号为6到36(31个节)。 具体: 11): 连接超级节6(因为k=0,第0位为12(5): 二进制101,第0位和第2位为1,所以连接超级节6(k=0)和8(k=2) 3(6): 二进制110,第1位和第2位为1,所以连接超级节7(k=1)和8(k=2) 4(7): 二进制111,第0,1,2位为1,所以连接超级节6,7,8 5(8): 二进制1000,第3位为1,所以连接超级节9(k=3) 然后,运行Dijkstra: dis[1]=0 从1出发,更新所有邻居:超级节6,距离为0+a[1]=1。 然后,从超级节6出发,更新所有邻居:1(但已经访问过,跳过),2(距离1+a[2]=1+5=6),4(距离1+a[4]=1+7=8)。 所以,dis[2]=6, dis[4]=8。 然后,从2出发(距离6),更新它的邻居:超级节6(已经访问),超级节8(距离6+a[2]=6+5=11)。 然后,从4出发(距离8),更新它的邻居:超级节6(已访问),超级节7(距离8+a[4]=8+7=15),超级节8(距离8+a[4]=8+7=15)。但是超级节8已经被2更新为11?所以这里会更新超级节7和8(但超级节8之前是11,现在从4更新为15,不更新)。 然后,从超级节8(距离11)出发,更新它的邻居:2(已访问),3(距离11+a[3]=11+6=17),4(距离11+a[4]=11+7=18,但4已经是8,所以不更新)。 然后,从3出发(距离17),更新邻居:超级节7(距离17+6=23),超级节8(距离17+6=23)。然后更新超级节7:从超级节7更新4(距离23+7=30,但4已经是8,所以不更新)和3(已访问)。 另外,超级节7(距离15)在之前被4更新过,然后从超级节7出发,更新3(距离15+6=21)和4(15+7=22,但4已经是8,不更新)。但是,3在超级节8更新时已经得到17,所以21>17,不会更新。 所以,最终: dis[1]=0 dis[2]=6 dis[3]=17 dis[4]=8 dis[5]:没有被更新,所以为INF,输出-1。 符合样例输出。 注意:在Dijkstra中,我们可能会先更新超级节,然后再由超级节更新其他原始节。所以,超级节也是图中的节,需要参更新。 但是,我们不需要输出超级节的距离。 另外,注意:同一个超级节可能被多个原始节更新,所以可能会多次进入优先队列,但我们用dis数组记录最小值,当从优先队列中取出一个节时,如果它的距离已经比dis数组中的值大,就可以跳过。 代码实现: 使用vector邻接表,注意节数最大为n+31=100000+31=100031。 边数:每个原始节最多连31条边,所以总边数不超过31*2*n(双向边,所以乘2?不对,每条边存两次:对于原始节i,添加一条从i到超级节的边,同时也会在超级节的邻接表中添加一条从超级节到i的边。所以总边数约为2*31*n,即620万条边。在C++中,使用vector邻接表,空间大约为620万*2(因为每个边存两次)?不对,在邻接表中,每条边在起和终各存一次?不对,无向边,我们在添加时: adj[i].push_back({super, a[i]}); adj[super].push_back({i, a[i]}); 所以,每条无向边被拆成两条有向边,所以总边数(有向边)为2*(31*n) = 62*n,即62*100000=620万条边。在C++中,vector邻接表可以存储,但要注意空间(大约620万条边,每条边是一个pair<int, long long>,大约16字节,总空间约100MB,题目内存512MB,足够)。 然后,优先队列中最多有100031个节,但每次扩展会加入多个节,但每个节最多被扩展一次(标记后不再扩展),所以总操作次数为边数(即620万次更新,优先队列操作次数为O(m log n),log(100031)约17,所以总操作次数约1亿,在C++中可能勉强1秒(在优化常数的情况下)。 注意:使用优先队列(堆)的Dijkstra,每个节只扩展一次(用vis数组标记),但更新距离时,同一个节可能被多次加入优先队列(但只有第一次取出时扩展),所以优先队列中最多有O(m)个元素。 代码框架: #include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 100000 + 31; // 节数:n+31 const ll INF = 1e18; int n; ll a[100000+10]; // 原始数组,1-indexed vector<pair<int, ll>> adj[MAXN+10]; // 邻接表,节编号1到n+31 ll dis[MAXN+10]; bool vis[MAXN+10]; // 标记是否已确定最短路 void dijkstra(int start) { for (int i = 1; i <= n+31; i++) { // 初始化,节1到n+31 dis[i] = INF; vis[i] = false; } dis[start] = 0; priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> pq; pq.push({0, start}); while (!pq.empty()) { ll d = pq.top().first; int u = pq.top().second; pq.pop(); if (vis[u]) continue; vis[u] = true; for (auto &edge : adj[u]) { int v = edge.first; ll w = edge.second; if (dis[v] > d + w) { dis[v] = d + w; pq.push({dis[v], v}); } } } } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%lld", &a[i]); } // 初始化邻接表 // 注意:节编号:原始节1~n,超级节n+1~n+31(对应位0~30) // 清空邻接表,但MAXN是100031,所以从1到n+31 for (int i = 1; i <= n+31; i++) { adj[i].clear(); } // 对于每个原始节i,枚举二进制位 for (int i = 1; i <= n; i++) { if (a[i] == 0) { // 0没有边,跳过 continue; } for (int k = 0; k <= 30; k++) { // 检查第k位是否为1 if (a[i] & (1LL << k)) { int super = n + 1 + k; // 超级节编号 // 添加无向边:i到super,边权为a[i] adj[i].push_back({super, a[i]}); adj[super].push_back({i, a[i]}); } } } dijkstra(1); // 输出结果:对于每个原始节i(1~n) for (int i = 1; i <= n; i++) { if (dis[i] == INF) { printf("-1"); } else { printf("%lld", dis[i]); } if (i < n) printf(" "); else printf("\n"); } return 0; } 注意:超级节编号:k从0到30,超级节编号为n+1+k。所以当n=5时,超级节编号为6,7,...,36(共31个)。但我们的节总数是n+31=36,所以数组要开够。 但是,在邻接表中,我们给节编号1~n+31(即1~36)都分配了空间,所以没有问题。 另外,注意:1<<k 对于k=30,可能会溢出int,所以用1LL<<k。 样例测试:n=5,a[1]=1, a[2]=5, a[3]=6, a[4]=7, a[5]=8。 运行后,dis[1]=0, dis[2]=6, dis[3]=17, dis[4]=8, dis[5]=INF(-1)。 符合。 但是,还有一种情况:起1的a[1]为0,那么它不会连接任何超级节,所以只能到达自己。 测试:n=1, a[1]=0,则输出0。 另外,注意:如果两个原始节之间没有公共二进制位,则无法到达,输出-1。 再测试:n=2, a[1]=1, a[2]=2。1和2:二进制0110,按位为0,所以没有边。所以1到点2不可达,输出0 -1?不对,1到点1是0,1到点2不可达。 建图:11)连接超级节(第0位)n+1(即2+1=3?n=2,超级节编号3)。 2(2)连接超级节(第1位)n+1+1=4。 所以12没有公共超级节,所以2不会被更新。 输出:0 -1。 正确。 但是,还有一种路径1->超级节3->1->超级节3->...,但这样不会更新2。 所以,算法正确。 最后,注意:a[i]可能为0,所以跳过建边。但是,起1的a[1]可能为0,我们仍然设置dis[1]=0,然后它没有边连出去,所以其他都是INF。 因此,代码完整。 但是,在Dijkstra中,我们使用了vis数组,确保每个节只被扩展一次,这是正确的。 另外,优先队列中可能有很多冗余节,但用vis数组跳过,可以避免重复扩展。 现在,提交代码时,注意输入输出效率,使用scanf和printf。 由于n最大10^5,边数620万,Dijkstra算法时间复杂度O(m log n),大约1e6*log(1e5)≈1e6*17=1.7e7次操作,在C++中应该可以1秒内完成。 但是,常数较大(优先队列操作),所以注意使用较快的IO。 另外,注意:超级节编号从n+1到n+31,所以节总数是n+31,不要开小数组。 最后,输出格式:第i个为1到i的最短路长度,空格分隔,行末无空格。 代码: 注意:在添加边时,如果a[i]为0,则跳过。另外,超级节编号:n+1+k(k从0到30)。 我们使用0-indexed的k(0~30)代表31个二进制位。 提交代码,注意内存:邻接表大约620万条边,每个pair<int,ll>是12字节(int4,ll8),总空间约6200000*12=74.4MB,加上其他数组,应该不会超过512MB。 运行时间:优先队列操作约620万次push和pop,每次O(log(100031))≈17次操作,总操作次数约1亿,在C++中1秒可能有紧,但应该能过(在优化编译下)。 如果超时,可以尝试使用vector邻接表时,预先保留空间。 但本题n最大10万,边数620万,vector的push_back可能会引起多次重新分配,所以可以预先reserve: for (int i = 1; i <= n+31; i++) { adj[i].reserve(50); // 原始节最多31条边,超级节最多有多少边?一个超级节可能被很多原始节连接,但无法预知,所以不reserve也可以。 } 或者,在添加边时,每个原始节最多31条边,但超级节的边数可能很多(如果很多数都有某一位,那么该超级节的度就很大)。但总边数固定,所以vector的push_back总时间O(m),可以接受。 下面写代码。 注意:在Dijkstra中,优先队列用greater,所以pair的第一个元素是距离,第二个是节。 另外,距离用long long。 最后,输出时,每个原始节的距离,如果是INF则输出-1。 代码:</think>### 问题分析 题目要计算从节1到所有节的最短路径,其中节i和j之间存在边的条件是a[i] & a[j] ≠ 0(按位不为零),边权为a[i] + a[j]。由于直接建图会导致边数达到O(n²),需优化建图。 #### 优化思路 利用二进制位性质:若两个数在某一位均为1,则它们有边。为每个二进制位k(0≤k≤30)创建超级节(编号n+1+k)。对于每个节i: - 若a[i]的第k位为1,则在i和超级节n+1+k间添加无向边,边权为a[i]。 这样,若两个节有公共位k,则路径i→(n+1+k)→j的边权和为a[i] + a[j],直接边等价。 #### 算法步骤 1. **建图**: - 原始节1~n - 超级节:n+1 ~ n+31(对应31个二进制位) - 对每个非零a[i],枚举其二进制位k,添加边(i, n+1+k, a[i])和反向边。 2. **Dijkstra最短路**: - 初始化距离数组dis,起dis[1]=0。 - 使用优先队列(小根堆)按距离升序处理节。 3. **输出**:对每个原始节i,输出dis[i](若未更新则输出-1)。 #### 复杂度 - 节数:n + 31 - 边数:≤ 31 × 2 × n = 62n(约620万条) - 时间复杂度:O((n + m) log n),其中m为边数,可接受。 ### 代码实现 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 100000 + 31; const ll INF = 1e18; int n; ll a[100010]; vector<pair<int, ll>> adj[MAXN]; ll dis[MAXN]; bool vis[MAXN]; void dijkstra(int start) { for (int i = 1; i <= n + 31; i++) { dis[i] = INF; vis[i] = false; } dis[start] = 0; priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> pq; pq.push({0, start}); while (!pq.empty()) { ll d = pq.top().first; int u = pq.top().second; pq.pop(); if (vis[u]) continue; vis[u] = true; for (auto &edge : adj[u]) { int v = edge.first; ll w = edge.second; if (dis[v] > d + w) { dis[v] = d + w; pq.push({dis[v], v}); } } } } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%lld", &a[i]); } for (int i = 1; i <= n + 31; i++) { adj[i].clear(); } for (int i = 1; i <= n; i++) { if (a[i] == 0) continue; for (int k = 0; k <= 30; k++) { if (a[i] & (1LL << k)) { int super = n + 1 + k; adj[i].push_back({super, a[i]}); adj[super].push_back({i, a[i]}); } } } dijkstra(1); for (int i = 1; i <= n; i++) { if (dis[i] == INF) printf("-1"); else printf("%lld", dis[i]); if (i < n) printf(" "); else printf("\n"); } return 0; } ``` ### 示例验证 **输入**: ``` 5 1 5 6 7 8 ``` **输出**:`0 6 17 8 -1` - 节1:距离0 - 节2:路径1→(位0)→2,边权1+5=6 - 节3:路径1→2→3,边权(1+5)+(5+6)=17 - 节4:路径1→(位0)→4,边权1+7=8 - 节5:无公共位,不可达
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值