最短路

图论中的经典问题,由一个点到另外一个点所经过的最短路径是多少。

BFS

对于每个边都相同的,可以统计从初始点到目标点经过的路径数量。

直接使用BFS即可

int dis[maxn];
queue<int> que;
vector<int> e[maxn];
void bfs()
{
    while(!que.empty())que.pop();
    for(int i=2;i<=n;i++)dis[i]=inf;
    dis[1]=0;
    que.push(1);
    while(!que.empty())
    {
        int u=que.front();que.pop();
        int s=e[u].size();
        for(int i=0;i<s;i++)
        {
            int v=e[u][i];
            if(dis[v]==inf)
            {
                que.push(v);
                dis[v]=dis[u]+1;
            }
        }
    }
}

补图的BFS

已知图G,对于图G的补图进行BFS的方法,利用set集合进行处理(来快速获取对应补边)!

S1集合存储当前还没有访问过的点集合;

从某点出发,边分成两部分(一种是G边,一种是G补图边),遍历当前点的G边,去掉已经访问过的,图G边放入s2,s1删除该点,则

S1表示当前还没有访问过的点集合且为G补图可到点

S2表示当前还没有访问过的点集合且为G可到点

就可以更新s1中的所有点了。

再将S2中的点存放进入S1

void bfs()
{
    set<int> s1,s2;
    set<int>::iterator it;
    s1.clear();s2.clear();
    dis[1]=0;
    for(int i=2;i<=n;i++)
    {
        dis[i]=inf;
        s1.insert(i);
    }
    while(!que.empty())que.pop();
    que.push(1);
    while(!que.empty())
    {
        int u=que.front();que.pop();
        int s=e[u].size();
        for(int i=0;i<s;i++)
        {
            int v=e[u][i];
            if(s1.count(v)==0)continue;
            s1.erase(v);
            s2.insert(v);
        }
        for(it=s1.begin();it!=s1.end();it++)
        {
            dis[*it]=dis[u]+1;
            que.push(*it);
        }
        s1.swap(s2);
        s2.clear();
    }
}

SPFA

带有负边,但是没有负环的图,求单源最短路径。

struct edge
{
    int to;
    ll w;
    edge(){}
    edge(int a,ll b){to=a;w=b;}
};
vector<edge> e[maxn];

bool vis[maxn];
ll dis[maxn];
int enq[maxn];  //存储结点被放入循环的次数
bool spfa(int s)
{
    memset(vis,0,sizeof(vis));
    memset(enq,0,sizeof(enq));
    for(int i=1;i<=n;i++)dis[i]=inf;

    queue<int> q;
    q.push(s);
    dis[s]=0;vis[s]=true;enq[s]++;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        vis[u]=false;
        int s=e[u].size();
        for(int i=0;i<s;i++)
        {
            int v=e[u][i].to;
            if(dis[v]>dis[u]+e[u][i].w)
            {
                dis[v]=dis[u]+e[u][i].w;
                if(vis[v]==false)
                {
                    q.push(v);
                    vis[v]=true;enq[v]++;
                    if(enq[v]>=n)return false;
                }
            }
        }
    }
    return true;
}

有很多基于SPFA的优化,典型的就是利用双端队列,比较加入结点和头结点的大小,如果小,则加入到头部,否则加入到尾部,这一个小小的优化就可以提升极大的速度。

Dijkstra

迪杰斯特拉采用的是基于贪心的算法,每次寻找还未访问的节点中距离起始结点最短的一点进行距离更新,最终将该点到其他所有可到达点的最短距离求出。

限制:无法处理该图中带有负边的情况

int mmap[maxn][maxn];  

/*
struct edge
{
    int to;
    int w;
    edge(){}
    edge(int a,int b){to=a;w=b;}
};
vector<edge> vec[maxn];   //也可以用vector写
*/

int vis[maxn];
int dis[maxn];
//int path[maxn];        //可以添加路径记录
void dijkstra(int u)
{
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
        dis[i]=inf;

    dis[u]=0;
    //path[u]=0;
    while(true)
    {
        int v=-1;
        for(int i=1;i<=n;i++)
            if(vis[i]==false && (v==-1 || dis[i]<dis[v]))
                v=i;

        if(v==-1)
            break;
        vis[v]=true;

/*
        for(int i=0;i<vec[v].size();i++)    //vector实现的路径更新
        {
            int to=vec[v][i].to;
            if(vis[to]==false && dis[to]>dis[v]+vec[u][i].w)
            {
                dis[to]=dis[v]+vec[u][i].w;
                //path[to]=v;             //路径记录
            }
        }
*/

        for(int i=1;i<=n;i++)         //邻接表实现的路径更新
            if(vis[i]==false && mmap[v][i]!=inf)
                if(dis[i]>dis[v]+mmap[v][i])
                {
                    dis[i]=dis[v]+mmap[v][i];
                    //path[i]=v;         //路径记录
                }
    }
}

优先队列优化的Dijkstra

struct edge
{
    int to;
    ll w;
    edge(){}
    edge(int a,ll b){to=a;w=b;}
};
vector<edge> e[maxn];

struct qnode
{
    int id;
    int dis;
    qnode(){}
    qnode(int a,int b){id=a;dis=b;}
    bool operator<(const qnode &b)const{return dis>b.dis;}
};
priority_queue<qnode> que;
int dis[maxn];
void dijkstra(int start)
{
    for(int i=1;i<=n;i++)
        dis[i]=inf;

    while(!que.empty())
        que.pop();
    dis[start]=0;

    que.push(qnode(start,0));
    while(!que.empty())
    {
        qnode tmp=que.top();
        que.pop();
        int id=tmp.id;
        if(dis[id]<tmp.dis)
            continue;

        int s=e[id].size();
        for(int i=0;i<s;i++)
        {
            int to=e[id][i].to;     //id点能到达的其他点
            if(dis[id]+e[id][i].w<dis[to])  //对比一下距离关系
            {
                dis[to]=dis[id]+e[id][i].w;
                que.push(qnode(to,dis[to]));
            }
        }
    }
}

边优化+优先队列优化+基于状态可dp的Dijkstra

struct edge
{
    int t;
    ll w;
    int next;
};
edge e[maxm<<1];
int head[maxn];
int tot;
void init()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,ll w)
{
    e[tot].t=v;
    e[tot].w=w;
    e[tot].next=head[u];
    head[u]=tot;
    tot++;
}


struct qnode
{
    int id;
    int state;
    ll dis;
    qnode(){}
    qnode(int a,int b,ll c):id(a),state(b),dis(c){}
    bool operator<(const qnode &b)const{return dis>b.dis;}
};
priority_queue<qnode> que;
ll dis[maxn][maxm]; //dis(node,state)
void dijkstra(int start)
{
    int maxstate=1000;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=maxstate;j++)
            dis[i][j]=inf;

    while(!que.empty())
        que.pop();
    dis[start][1/*init_state*/]=0;

    que.push(qnode(start,1/*init_state*/,0));
    while(!que.empty())
    {
        qnode tmp=que.top();
        que.pop();
        int u=tmp.id;
        int usta=tmp.state;
        if(dis[u][usta]<tmp.dis)
            continue;

        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int v=e[i].t;     //id点能到达的其他点
            ll w=e[i].w;
            int nsta=100/*nextstate*/;

            if(dis[u][usta]+w<dis[v][nsta])  //对比一下距离关系
            {
                dis[v][nsta]=dis[u][usta]+w;
                que.push(qnode(v,nsta,dis[v][nsta]));
            }
        }
    }
}

floyed算法

void  floyed()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}

传递闭包

floyed_warshall算法,类似于floyed算法

利用该算法,很容易从可达矩阵中看出整个图的联通情况和强连通分量(强连通的优化算法为tarjan)

void  floyed()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                g[i][j]=(g[i][j]||(g[i][k] && g[k][j]));
}

floyed强连通的判定方法,依次遍历所有点,选取未被遍历过的u点作为基准点,从剩下未被遍历过的点中任选一个点v,如果g[u][v]=g[v][u]=1,则将v点加入该点集,因为他们可以相互到达点。

应用

1.单向路径求从某结点出发到其他点又回来

POJ - 1511

u到任意点v的距离等于u->v+v->u;所以我们可以从u开始求一次最短路将u->v的值都求出来。

关键就是求v->u的就过,如果对每个v求一次最短路,肯定超时,我们不妨思考一下,其实就是将所有边全部反向,再从u走向其他点v,就是v->u的结果。

2.由一个点走到两个其他点所经过不同点数最少的方案。

Gym - 101170I 

由于关系的是三个不同点,因此我们不能看成点S到t1的最短路加到t2的最短路,因为可能有重复部分,故我们想怎么去重,也即是去掉点S到分叉路口的长度。深入思考,即可得到,本质上是寻找一个点到S,t1,t2距离和最短的那个点,有贪心的思想在里面。

结合应用1的做法,建立正反向图,三次BFS即可。

3.第K短路

由于dij过程中s到e的第k短路就是优先队列中第几次pop()e点,因此只需要不停地运行即可, 但由于单纯的不断运算可能会MLE或者T,因此我们需要进行一定的优化,用A*思想进行优化,即优先队列采用H(x)=g(x)+f(x);g(x)为当前dij距离,f(x)为所在地安到e的最短距离,即可贪心优化。

int n,m;
int s,e,k,t;  //从s到e第k短路是否小于等于长度t
struct edge
{
	int to;
	int w;
	int next;
	edge(){}
	edge(int a,int b,int c){to=a;w=b;next=c;}
};
struct Map
{
        int cnt;
        int head[maxn];
        edge e[maxn<<1];
        void init(){cnt=0;memset(head,-1,sizeof(head));}
        void add(int u,int v,int w)
        {
            e[cnt]=edge(v,w,head[u]);
            head[u]=cnt++;
        }
        edge &operator[](int p){return e[p];}
};
Map g,rg;

ll dis[maxn],vis[maxn];
void spfa()
{
	memset(dis,inf,sizeof(dis));
	memset(vis,0,sizeof(vis));
	queue<int>q;
	q.push(e);
	dis[e]=0,vis[e]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		vis[u]=0;
		for(int i=rg.head[u];i!=-1;i=rg[i].next)
		{
			int to=rg[i].to,w=rg[i].w;
			if(dis[to]>dis[u]+w)
			{
				dis[to]=dis[u]+w;
				if(vis[to]==false)
				{
					q.push(to);
					vis[to]=1;
				}
			}
		}
	}
}
struct sta
{
	int p;
	ll d;
	sta(int a,ll b){p=a;d=b;}
};
bool operator<(sta a,sta b)
{
    return a.d+dis[a.p]>b.d+dis[b.p];
}
ll Astar()
{
	if(dis[s]>=inf)return -1;
	priority_queue<sta> q;
	q.push(sta(s,0));
	int num=0;
	while(!q.empty())
	{
		sta tmp=q.top();q.pop();
		if(tmp.p==e)
		{
			num++;
			if(num==k)return tmp.d;
		}
		for(int i=g.head[tmp.p];i!=-1;i=g[i].next)q.push(sta(g[i].to,g[i].w+tmp.d));
	}
	return -1;
}
int main()
{
	while(scanf("%d %d",&n,&m)!=EOF)
	{
	    scanf("%d %d %d %d",&s,&e,&k,&t);
	    g.init(),rg.init();
		for(int i=0;i<m;i++)
		{
			int f,t,w;
			scanf("%d %d %d",&f,&t,&w);
			g.add(f,t,w);
			rg.add(t,f,w);
		}
		spfa();
		ll ans=Astar();
		if(ans==-1||ans>t)puts("NO!");
		else puts("YES!");
	}
	return 0;
 }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值