杭电oj Choose the best route

48 阅读4分钟

Choose the best route

这道题是杭电oj的一道题,这是一道多起点单终点的最短路问题,在这里会提供两种办法解决这种类型的问题,第一种是反向建边,第二种是超级源点。

1.反向建边

反向建边是一个很妙的操作(下面的超级源点也很妙),因为不可能调用多次Dijkstra来确定一堆起点到终点的最短路,这样肯定会tle(floyd就更不用说了),所以我们可以反过来想,将唯一的终点换成起点,将多个起点换成多个终点,这样一来只用调用一次Dijkstra就可以求出所有到唯一终点的点的最短路金,最后进行比较即可。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Os")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("Og")
#pragma GCC optimize("inline")
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair <ll , ll> pii;
typedef priority_queue <ll ,vector<ll> ,greater<ll> > xiao;  
typedef priority_queue <ll ,vector<ll> ,less<ll> > da; 
const int N=5e4 + 10,inf = 0x3f3f3f3f;
const ull P = 131;

int h[N],ne[N],e[N],idx,w[N];
int dist[N],en[N];
int n,m,s;
bool st[N];

void add(int a,int b,int c)
{
    e[idx] = b;
    ne[idx] = h[a];
    w[idx] = c;
    h[a] = idx++;
}

void Dijkstra()
{
    dist[s] = 0;     //起始点设置为终点 
    priority_queue <pii , vector<pii> , greater<pii> > heap;
    heap.push({0 , s});
    
    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();
        int ver = t.second,distance = t.first;
        
        if(dist[ver] < distance)
        {
            continue;
        }
        
        for(int i = h[ver] ; i != -1 ; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j] , j});
            }
        }
    }
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0),cout.tie(0);
    
    while(cin>>n>>m>>s)
    {
    	memset(h , -1 , sizeof h);
        memset(dist , inf , sizeof dist);
        idx = 0;
        
        while(m--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            add(b , a , c);   //反向建边 
        }
        
        Dijkstra();     //求出来的是所有点到终点最短路径 
        
        int w;
        cin>>w;
        int ans = inf;
        
        while(w--)
        {
            int x;
            cin>>x;
            ans = min(ans , dist[x]);
        }
        
        if(ans == inf)
        {
            ans = -1;
        }
        cout<<ans<<"\n";
    }
}

以上便是反向建边的写法,关于反向建边还有一道例题

P3916 图的遍历

这道题因为是要返回途径最大编号点的编号,如果是正常的按顺序dfs整个图那么就要遍历n次图,肯定会tle,这种时候我们可以调转思路,运用逆向思维反向建图,然后从大编号开始遍历,每个编号遍历的时候把途径的点都给标记上,这样之后每次遍历的时候只用遍历之前没有被标记过的点就行了

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair <ll , ll> pii;
typedef priority_queue <ll ,vector<ll> ,greater<ll> > xiao;  
typedef priority_queue <ll ,vector<ll> ,less<ll> > da; 
const int N=2e6 + 10,M = 0x3f3f3f3f;
const ull P = 131;

int n,m;
int h[N],e[N],ne[N],idx,res[N];
bool st[N];

void add(int a,int b)
{
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}

void dfs(int u,int k)
{
	st[u] = 1;
	res[u] = max(res[u] , k);
	for(int i = h[u] ; i != -1 ; i = ne[i])
	{
		int j = e[i];
		if(!st[j])
		{
			res[j] = max(res[j] , k);
			dfs(j , k);
		}
	}
}

int main()
{     
    std::ios::sync_with_stdio(false);
	std::cin.tie(0),cout.tie(0);
	
	memset(h , -1 , sizeof(h));
	cin>>n>>m;
	for(int i = 1 ; i <= m ; i++)
	{
		int a,b;
		cin>>a>>b;
		add(b , a);
	}
	
	for(int i = n ; i >= 1 ; i--)
	{
		if(!res[i])
		{
			dfs(i , i);
		}
	}
	
	for(int i = 1 ; i <= n ; i++)
	{
		cout<<res[i]<<" ";
	}
}

2.超级源点

接下来为大家带来第二种解决这种多起点问题的方法,就是超级源点,所谓的超级源点就是在建完图以后再额外设置一个虚点0,这个点到所有可能的起点的权值都是0,之后使用Dijkstra的时候只需要把起点设置为这个新建立的虚点,然后求出这个虚点到每个点的最小路径,这样只需要跑一次Dijkstra即可,非常的巧妙。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Os")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("Og")
#pragma GCC optimize("inline")
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair <ll , ll> pii;
typedef priority_queue <ll ,vector<ll> ,greater<ll> > xiao;  
typedef priority_queue <ll ,vector<ll> ,less<ll> > da; 
const int N=2e5 + 10,inf = 0x3f3f3f3f;
const ull P = 131;

int h[N],e[N],ne[N],idx,w[N];
int dist[N];
bool st[N];
int n,m,s;

void add(int a,int b,int c)
{
	e[idx] = b;
	ne[idx] = h[a];
	w[idx] = c;
	h[a] = idx++;
}

int Dijkstra()
{

	dist[0] = 0;
	priority_queue <pii , vector<pii> , greater<pii> > heap;
	heap.push({0 , 0});
	
	while(heap.size())
	{
		auto t = heap.top();
		heap.pop();
		int ver = t.second,distance = t.first;
	
		if(st[ver])
		{
			continue;
		}
		
		st[ver] = 1;
		
		for(int i = h[ver] ; i != -1 ; i = ne[i])
		{
			int j = e[i];
			if(dist[j] > distance + w[i])
			{
				dist[j] = distance + w[i];
				heap.push({dist[j] , j});
			}
		}
	}
	
	if(dist[s] == inf)
	{
		return -1;
	}else
	{
		return dist[s];
	}
}

int main()
{
	std::ios::sync_with_stdio(false);
    std::cin.tie(0),cout.tie(0);

	while(cin>>n>>m>>s)
	{
		memset(h , -1 , sizeof h);
        memset(dist , inf , sizeof dist);
        memset(st , 0 , sizeof st);
        idx = 0;
		
		while(m--)
		{
			int a,b,c;
			cin>>a>>b>>c;
			add(a , b , c);
		}
		int w;
		cin>>w;
		while(w--)    //设置超级源点 
		{
			int a;
			cin>>a;
			add(0 , a , 0);
		}
		
		int t = Dijkstra();
		
		if(t == -1)
		{
			cout<<-1<<"\n";
		}else
		{
			cout<<t<<"\n";
		}
	}
}