最短路康复训练

最短路

金字塔

比平常的最短路多加了个参数。
这里的路径长度计算与其他题不同,它是一条路径的长度+这条路径中最长的那条路的长度
用平常的最短路是行不通的,不能设置平常的vis数组,即(点出队列就不再更新)这个思路是错的,因为加上路径上的路的max可能就不一样了。

代码

void dijkstra(int s) 
{
	for(int i=1;i<=get(1,n);i++) dis[i]=inf,mx[i]=0;
	dis[s]=0;
	priority_queue<pii> q;
	q.push({0,s});
	while(q.size()) 
	{
		int u=q.top().second;
		q.pop();
		//if(vis[u]) continue;
		//vis[u]=1;
		for(int i=h[u];i!=-1;i=ne[i]) 
		{
			int j=e[i];
			if(dis[j]+mx[j]>dis[u]+w[i]+max(mx[u],w[i])) 
			{
				dis[j]=dis[u]+w[i];
				mx[j]=max(mx[u],w[i]);
				if(!vis[j]) q.push({-dis[j],j});
			}
		}
	}
}

最短路树

思路

先dijkstra求一下点1到各个点的距离,然后扫一遍图,把dijkstra的最短路树弄出来,求出\(cnt[i]\),有多少个点经过i到点1,如果连接(1,i),则节省的长度为\(cnt[i]*(t-dis[i])\)
该题还有个条件,字典序,从1到n枚举点,这样就保证了字典序

代码

const int N=2e5+10,inf=1e18;
int n,m,k;
int h[N],e[N*2],ne[N*2],w[N*2],idx;
int dis[N],cnt[N],pre[N];
bool vis[N];
vector<int> g[N];//存最短路树
void add(int a,int b,int c) 
{
	e[idx]=b;
	w[idx]=c;
	ne[idx]=h[a];
	h[a]=idx++;
} 

void dijkstra(int s) 
{
	for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
	dis[s]=0;
	priority_queue<pii> q;
	q.push({0,s});
	while(q.size()) 
	{
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=h[u];i!=-1;i=ne[i]) 
		{
			int j=e[i];
			if(dis[j]>dis[u]+w[i]) 
			{
				dis[j]=dis[u]+w[i];
				if(!vis[j]) q.push({-dis[j],j});
			}
		}
	}
}

void dfs(int u,int fa) 
{
	for(int i=0;i<g[u].size();i++) 
	{
		int j=g[u][i];
		if(j==fa) continue;
		dfs(j,u);
		cnt[u]+=cnt[j]; 
	}
}

void solve() 
{	
	memset(h,-1,sizeof(h));
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++) cin>>cnt[i];
	for(int i=1;i<=m;i++) 
	{
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
		add(b,a,c);
	}
	dijkstra(1);
	int ans=0;
	for(int u=1;u<=n;u++) 
	{
		for(int i=h[u];i!=-1;i=ne[i]) 
		{
			int j=e[i];
			//pre数组是如果dis[j]可以由多个点转移过来,则选最小的
			if(dis[j]==dis[u]+w[i]&&!pre[j]) 
			{
				g[u].push_back(j);
				pre[j]=u;
			}
		}
	}
	dfs(1,-1);
	for(int i=1;i<=n;i++) 
	{
		ans=max(ans,cnt[i]*(dis[i]-k));
	}
	cout<<ans<<endl;
}

通往奥格瑞玛的道路

题意

给出一个图,图有两个信息:每个城市会收钱,每条路会扣血,主角血条为K,问从1走到n,血不扣到负数的情况下,经过的城市收费最大值的最小值。

思路

二分+最短路。一开始subtask1中第三个点老是过不去,最后看题解,???我一开始就在城市1它都要收费啊?

代码

bool dijkstra(int s,int x) 
{	
	for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
	priority_queue<pii> q;
	dis[s]=0;
	q.push({0,s});
	while(q.size()) 
	{
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=h[u];i!=-1;i=ne[i]) 
		{
			int j=e[i];
			if(f[j]>x) continue;
			if(dis[j]>dis[u]+w[i]) 
			{
				dis[j]=dis[u]+w[i];
				if(!vis[j]) q.push({-dis[j],j});
			}
		}
	}
	return dis[n]<=k;
}

void solve() 
{		
	memset(h,-1,sizeof(h));
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++) cin>>f[i];
	for(int i=1;i<=m;i++) 
	{
		int a,b,c;
		cin>>a>>b>>c;
		if(a==b) continue;
		add(a,b,c);
		add(b,a,c);
	}
	int l=f[1],r=inf;//l要从城市1收的钱开始
	while(l<r) 
	{
		int mid=(l+r)/2;
		if(dijkstra(1,mid)) r=mid;
		else l=mid+1;
	}
	if(r==inf) cout<<"AFK\n";
	else cout<<r<<endl;
}

P5837

题意

图中两个信息:每条管道的花费、流量,求1到n的流量与花费之比的最大值。

思路

首先花费能小就小,这就是求最短路了。
在上面的基础上增加约束条件:流量,从1到n的总流量取的是这条路中的管道的最小值,
题目说花费和流量都是1~1000的正整数,直接枚举最小流量做1000遍最短路。
好在它数据范围比较小,大点就用二分。
同类型题:P3063
上面的那题也差不多

代码

double dijkstra(int s,int x) 
{	
	for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
	priority_queue<pii> q;
	dis[s]=0;
	q.push({0,s});
	while(q.size()) 
	{
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=h[u];i!=-1;i=ne[i]) 
		{
			int j=e[i];
			if(f[i]<x) continue;//x已经是最小了,别比它更小
			if(dis[j]>dis[u]+w[i]) 
			{	
				dis[j]=dis[u]+w[i];
				if(!vis[j]) q.push({-dis[j],j});
			}
		}
	}
	if(dis[n]==inf) return -1;
	return x*1.0/dis[n];
}

void solve() 
{		
	memset(h,-1,sizeof(h));
	cin>>n>>m;
	for(int i=1;i<=m;i++) 
	{
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		if(a==b) continue;
		add(a,b,c,d);
		add(b,a,c,d);
	}
	int ans=0;
	for(int i=1;i<=1000;i++) 
	{	
		int k=dijkstra(1,i)*1e6;
		ans=max(ans,k);
	}
	cout<<ans<<endl;
}

P2047

思路

范围只有100,处理各个点对间的问题。 floyd的拿手好戏。
求最短路的时候顺便把两个点间最短路的条数也处理一下。
之后就是根据题目要求算答案了

代码

void floyd() 
{
	for(int k=1;k<=n;k++) 
	{
		for(int i=1;i<=n;i++) 
		{
			for(int j=1;j<=n;j++) 
			{
				if(dis[i][j]>dis[i][k]+dis[k][j]) 
				{
					dis[i][j]=dis[i][k]+dis[k][j];
					e[i][j]=e[i][k]*e[k][j];
				}
				else if(dis[i][j]==dis[i][k]+dis[k][j])
				{
					e[i][j]+=e[i][k]*e[k][j]; 
				}
			}
		}
	}
	for(int k=1;k<=n;k++) 
	{
		for(int i=1;i<=n;i++) 
		{
			for(int j=1;j<=n;j++) 
			{
				if(i==j||j==k||i==k) continue;
				if(e[i][j]==0) continue;
				if(dis[i][j]==dis[i][k]+dis[k][j]) 
				{
					ans[k]+=1.0*(e[i][k]*e[k][j])/e[i][j];
				}
			}
		}
	}
}

void solve() 
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) 
	{
		for(int j=i+1;j<=n;j++) 
		{
			dis[i][j]=dis[j][i]=inf;
		}
	}
	for(int i=1;i<=m;i++) 
	{
		int a,b,c;
		cin>>a>>b>>c;
		dis[a][b]=dis[b][a]=min(dis[a][b],c);
		e[a][b]=e[b][a]=1;
	}
	floyd();
	for(int i=1;i<=n;i++) 
	{
		cout<<fixed<<setprecision(3)<<ans[i]<<endl;
	}
}

P2176

题意

原本有个图,问将某一条边加倍后,最短路与之前的最短路的最大差值

思路

先将原来的图求一遍最短路,然后把最短路内的所有边都记录,加倍的路就是这些路的其中一条,因为最短路外的边加倍那图的最短路也没变。
那如果原来的最短路不止一条上面的做法会不会失效?不会,假设有两条最短路,两种情况:
如果它们没有公共的边,那么选任何一条最短路的边来加倍,那之后的图最短路就是之前没改的那一条
如果它们有公共的边,那么最优解肯定选它们的公共边的某一条加倍(不一定要选公共边中最长的那条,想想为什么)

时间复杂度:n为100,原图最短路含边最多为n-1,做n-1遍最短路,
复杂度为\(O((n-1)(n+m)logm)\), \(100*(100+5000)*log_2{5000} \approx 1300*5100\)

代码

const int N=210,inf=1e18;
int n,m,k,s,t;
int e[N][N],pre[N],dis[N];
vector<int> g[N];
bool vis[N];
void dijkstra(int s,bool flag) 
{
	for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
	dis[s]=0;
	priority_queue<pii> q;
	q.push({0,s});
	while(q.size()) 
	{
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=0;i<g[u].size();i++) 
		{
			int j=g[u][i];
			if(dis[j]>dis[u]+e[u][j]) 
			{
				dis[j]=dis[u]+e[u][j];
				if(flag) pre[j]=u;
				if(!vis[j]) q.push({-dis[j],j});
			}
		}
	}
}

void solve() 
{	
	cin>>n>>m;
	for(int i=1;i<=m;i++) 
	{
		int a,b,c;
		cin>>a>>b>>c;
		e[a][b]=e[b][a]=c;
		g[a].push_back(b);
		g[b].push_back(a);
	}
	dijkstra(1,1);
	int k=dis[n],mx=0;
	int now=n;
	while(now!=1) 
	{	
		e[now][pre[now]]*=2;
		e[pre[now]][now]*=2;
		dijkstra(1,0);
		mx=max(mx,dis[n]-k);
		e[now][pre[now]]/=2;
		e[pre[now]][now]/=2;
		now=pre[now];
	}
	cout<<mx<<endl;
}

2023.4.21

这一个星期刷了下最短路的题,把有意思的题记录了一下。上个大学怎么这么多事啊,java课设、计组实验、数模论文、明天就是天梯赛了,这个星期可能是这个学期最累的一个星期了。

posted @ 2023-04-21 20:40  Liang2003  阅读(9)  评论(0编辑  收藏  举报