P8724 [蓝桥杯 2020 省 AB3] 限高杆 题解

题目传送门:P8724 [蓝桥杯 2020 省 AB3] 限高杆


解决方法:

基本思想是分层图,但提供一个与众不同的做法(双倍经验 P1849)。

定义两个队列 \(q,q_2\),其中,\(q\) 是跑 Dijkstra 所用的队列,\(q_2\) 记录有限高杆的路通向的点和距离(具体实现参考代码)。

邻接表存图,多开一个 \(D\) 记录这条路上是否有限高杆。

加点操作:

void add(int a,int b,int c,int d)
{//加点操作
	E[a].push_back(b);
	V[a].push_back(c);
	D[a].push_back(d);
}

在 Dijkstra 开始前,清空 \(q\),把 \(q_2\) 中的值附到 \(q\) 中,同时更新从起点到 \(q_2\) 中的点的最小距离,实现如下:

void copy_queue()
{
	while(!q.empty()) q.pop();
	while(!q2.empty()) q.push(q2.top()),dis[q2.top().second]=min(dis[q2.top().second],q2.top().first),q2.pop();
	//同时更新dis记录的最短路(类似延时更改)
}

Dijkstra 函数中,

  • 传入一个参数 \(cnt\),记录拆了几条限高杆。
  • 扩展边时,如果这条路有限高杆,就把它存入 \(q_2\),中;反之,进行 Dijkstra 的松弛操作。
  • 结束时,更新起点到终点的最短路。
void dij(int cnt)
{
	memset(vis,0,sizeof(vis));
	//清空vis数组
	copy_queue();//把 q_2 附给 q
	
	while(q.size())
	{
		int x=q.top().second;
		q.pop();
		
		if(vis[x]) continue;//到过这个节点
		vis[x]=1;//记录
		
		for(int i=0;i<E[x].size();i++)
		{
			int k=E[x][i];
			int w=V[x][i];
			int d=D[x][i];
			
			if(d) q2.push(make_pair(min(dis[k],dis[x]+w),k));
			//注意把长度取min,否则拆除后最短路无法保证
			else if(dis[k]>dis[x]+w)//松弛操作
			{
				dis[k]=dis[x]+w;
				q.push(make_pair(dis[k],k));
			}
		}
	}
	maxn=max(maxn,dis[n]);//这里是记录没拆限高杆时的最短路
	minn=min(minn,dis[n]);//拆限高杆后的最短路
	if(cnt<2) dij(cnt+1);//没拆够两个限高杆,继续拆。
}

完整代码:

#include<bits/stdc++.h>
#define reg register
#define int long long
using namespace std; 
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q,q2;

int n,m;
vector<int> E[100005],V[100005];
vector<int> D[100005];
int maxn=-999999999,minn=9999999999;
int dis[1<<20];
bool vis[1<<20];

void copy_queue()
{
	while(!q.empty()) q.pop();
	while(!q2.empty()) q.push(q2.top()),dis[q2.top().second]=min(dis[q2.top().second],q2.top().first),q2.pop();
	//同时更新dis记录的最短路(类似延时更改)
}

void add(int a,int b,int c,int d)
{//加点操作
	E[a].push_back(b);
	V[a].push_back(c);
	D[a].push_back(d);
}

void dij(int cnt)
{
	memset(vis,0,sizeof(vis));
	//清空vis数组
	copy_queue();//把 q_2 附给 q
	
	while(q.size())
	{
		int x=q.top().second;
		q.pop();
		
		if(vis[x]) continue;//到过这个节点
		vis[x]=1;//记录
		
		for(int i=0;i<E[x].size();i++)
		{
			int k=E[x][i];
			int w=V[x][i];
			int d=D[x][i];
			
			if(d) q2.push(make_pair(min(dis[k],dis[x]+w),k));
			//注意把长度取min,否则拆除后最短路无法保证
			else if(dis[k]>dis[x]+w)//松弛操作
			{
				dis[k]=dis[x]+w;
				q.push(make_pair(dis[k],k));
			}
		}
	}
	maxn=max(maxn,dis[n]);//这里是记录没拆限高杆时的最短路
	minn=min(minn,dis[n]);//拆限高杆后的最短路
	if(cnt<2) dij(cnt+1);//没拆够两个限高杆,继续拆。
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	//加速
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		add(a,b,c,d);
		add(b,a,c,d);
	}
	
	memset(dis,0x3f,sizeof(dis));
	//初始化dis为无穷大
	dis[1]=0;
	q2.push(make_pair(dis[1],1));
	//往q_2中加入起点,
	//经过copy_queue()后就在 q 中了
	dij(0);//从不拆限高杆开始枚举
	
	cout<<maxn-minn;
	return 0;
}
posted @ 2025-02-08 16:37  Wy_x  阅读(28)  评论(0)    收藏  举报