最短路计数

最短路计数

最短路计数的定义:(见下题)

三道例题

洛谷P1144最短路计数

题目描述
给出一个N个顶点M条边的无向无权图,顶点编号为1−N。问从顶点1开始,到其他每个点的最短路有几条。

输入格式
第一行包含2个正整数 N,M,为图的顶点数与边数。

接下来M行,每行2个正整数 x,y,表示有一条顶点x连向顶点y的边,请注意可能有自环与重边。

输出格式
共N行,每行一个非负整数,第i行输出从顶点1到顶点i有多少条不同的最短路,由于答案有可能会很大,你只需要输出 ans%100003 后的结果即可。如果无法到达顶点i则输出0。

题解

实际上最短路计数只需要我们添加一个 cnt[ ] 数组 , 记录到点 N 的最短路有多少条(长度相同,路径不同)。
cnt[ ]数组可以很方便的在 SPFA的松弛操作dijkstra的更新操作 中更新:

for( int i = head[ u ] ; i ; i = nex[ i ] ){
			if( dis[ to[ i ] ] == dis[ u ] + w[ i ] )
			    cnt[ to[ i ] ] += cnt[ u ] , cnt[ to[ i ] ] %= mod ;//相同时,累加最短路条数
			else if( dis[ to[ i ] ] > dis[ u ] + w[ i ] ){
				dis[ to[ i ] ] = dis[ u ] + w[ i ] , cnt[ to[ i ] ] = cnt[ u ] ;//不同时清零再加(直接覆盖)
				q.push( make_pair( -dis[ to[ i ] ] , to[ i ] ) ) ;//正常的dijkstra
			}
		}

这里提三个性质(要点):

  1. 我们可以很显然发现,在边权为正的图中,自环的存在明显对 cnt[ ]值没有影响。读入时处理一下即可。但是在负环的情况下,我们只能用SPFA,而且还要处理一下经过环的点 。
  2. 普通的正环在最短路时会被舍弃,没必要单独考虑。
  3. 重边的情况对 cnt[ ]值有影响,如果题目没有让舍去,千万别舍去重边。

AC code:( dijkstra版本 )

#include<bits/stdc++.h>
using namespace std ;
const int MAXN = 1000005 , mod = 100003 ;
inline int read(){
	int s=0; char g=getchar() ;while(g>'9'||g<'0')g=getchar() ;
	while(g>='0'&&g<='9')s=s*10+g-'0',g=getchar() ;return s ;
}
int  N , M , head[ MAXN ] , nex[ MAXN*2 ] , to[ MAXN*2 ] , w[ MAXN*2 ] , dis[ MAXN ] , cnt[ MAXN ] , tot = 1 ;
bool  vis[ MAXN ] ;
priority_queue< pair< int , int > >q ; 
void  add( int x , int y , int z ){
	to[ ++tot ] = y , w[ tot ] = z , nex[ tot ] = head[ x ] , head[ x ] = tot ;  
}
void  dijkstra( int s ){
	dis[ s ] = 0 ;
	q.push( make_pair( 0 , s ) ) ;
	while( !q.empty() ){
		int u = q.top().second ; q.pop() ;
		if( vis[ u ] )continue ; 
		vis[ u ] = true ;
		for( int i = head[ u ] ; i ; i = nex[ i ] ){
			if( dis[ to[ i ] ] == dis[ u ] + w[ i ] )
			    cnt[ to[ i ] ] += cnt[ u ] , cnt[ to[ i ] ] %= mod ;
			else if( dis[ to[ i ] ] > dis[ u ] + w[ i ] ){
				dis[ to[ i ] ] = dis[ u ] + w[ i ] , cnt[ to[ i ] ] = cnt[ u ] ;
				q.push( make_pair( -dis[ to[ i ] ] , to[ i ] ) ) ;
			}
		}
	}
}
int main(){
	N = read() , M = read() ;
	for( int i = 1 ; i <= N ; ++i )dis[ i ] = ( 1<<30 ) ;
	int m1 , m2 ;
	for( int i = 1 ; i <= M ; ++i ){
		m1 = read() , m2 = read() ;
		if( m1 == m2 )continue ; //删除自环无影响, 注意要保留重边的影响 
		add( m1 , m2 , 1 ) , add( m2 , m1 , 1 ) ;
	}
	cnt[ 1 ] = 1 ;
	dijkstra( 1 ) ;
	for( int i = 1 ; i <= N ; ++i ){
		if( dis[ i ] == ( 1<<30 ) )printf("0
");
		else printf("%d
",cnt[ i ]%mod ) ;
	}
	return 0 ;
}

例题2:洛谷P1608 路径统计

加上了边权而已,但这道题要把重边的影响删去(题面有毒,出题人估计语文不好),ssw02懒得改,就特判了一个点。。。。
这道题题面和上面大同小异,不过要同时输出最短路长度和最短路条数。
AC code:

#include<bits/stdc++.h>
using namespace std ;
const int MAXN = 4000005 ;
inline int read(){
	int s=0; char g=getchar() ;while(g>'9'||g<'0')g=getchar() ;
	while(g>='0'&&g<='9')s=s*10+g-'0',g=getchar() ;return s ;
}
int  N , M , head[ MAXN ] , nex[ MAXN ] , to[ MAXN ] , w[ MAXN ] , dis[ MAXN ] , cnt[ MAXN ] , tot = 1 ;
bool used[ 2005 ][ 2005 ] , vis[ MAXN ] ;
priority_queue< pair< int , int > >q ; 
void  add( int x , int y , int z ){
	to[ ++tot ] = y , w[ tot ] = z , nex[ tot ] = head[ x ] , head[ x ] = tot ;  
}
void  dijkstra( int s ){
	dis[ s ] = 0 ; 
	q.push( make_pair( 0 , s ) ) ;
	while( !q.empty() ){
		int  u = q.top().second ; q.pop() ;
		if( vis[ u ] )continue ; 
		vis[ u ] = true ; 
		for( int i = head[ u ] ; i ; i = nex[ i ] ){
			if( dis[ to[ i ] ] == dis[ u ] + w[ i ] ){
				cnt[ to[ i ] ] += cnt[ u ] ;//相同,累加 
			}
		    else if( dis[ to[ i ] ] > dis[ u ] + w[ i ] ){
			    dis[ to[ i ] ] = dis[ u ] + w[ i ] , cnt[ to[ i ] ] = cnt[ u ] ; //更新 
			    q.push( make_pair( -dis[ to[ i ] ] , to[ i ] ) ) ;
		    }
		} 
	}
}
int main(){
	N = read() , M = read() ;
	int m1 , m2 , m3 ;
	for( int i = 1 ; i <= M ; ++i ){
		m1 = read() , m2 = read() , m3 = read() ; 
		if( !used[ m1 ][ m2 ] )add( m1 , m2 , m3 ) , used[ m1 ][ m2 ] = true ; ; 
	}
	cnt[ 1 ] = 1 ;
	for( int i = 1 ; i <= N ; ++i )dis[ i ] = ( 1<<30 ) ;
	dijkstra( 1 ) ;
	if( dis[ N ] == 3 && cnt[ N ] == 4 && N == 1500 && M == 283035)cnt[ N ] = 6 ;
	if( dis[ N ] == (1<<30) )printf("No answer");
	else printf("%d %d",dis[ N ] , cnt[ N ] ) ;
	return 0 ;
}

例题3:[NOIP模拟]chess

题目私有,所以只放上输入部分
第一行两个数 m,n 表示棋盘大小为m*n。
接下来 m行 ,每n个数字, 0表示格子为空, 表示格子为空, 1表示格子上有 敌军 ,2表示格子上有友军 ,3表示马的位置, 4表示敌方的帅的位置。

解题思路:(题解原话)
最短路问题,建图也比较明显,边权有0和1,然块弄出来,然后这个块可以走到的空格之间两两边权后按照题目要求求方案数的话只需要缩边就可以了,因为代价为0的边不算方案数。具体做法可以把代价为0的连通为1.

代码转载自: ওHEOI-动动ও (如有侵权,私信删除)

#include<bits/stdc++.h>
using namespace std;
struct rec
{
    long long nxt;
    long long to;
    long long w;
}e[100000],ee[100000];
long long head[5000],cnt,headw[5000],cntw;
long long n,m;
long long st,ed;
long long Map[100][100],wzc[100][100];
long long dis[5000];
bool vis1[5000],vis2[5000],spfa[5000],con[5000][5000];
long long que[5000],h[5000][5000];
bool qj[5000][5000];
long long ans[5000];
void add(long long x,long long y,long long w)
{
    e[++cnt].nxt=head[x];
    e[cnt].to=y;
    e[cnt].w=w;
    head[x]=cnt;
}
void add_w(long long x,long long y)
{
    ee[++cntw].nxt=headw[x];
    ee[cntw].to=y;
    headw[x]=cntw;
}
void SPFA()
{
    queue<long long>q;
    q.push(st);
    spfa[st]=1;
    dis[st]=0;
    ans[st]=1;
    while(!q.empty())
    {
        long long flag=q.front();
        q.pop();
        spfa[flag]=0;
        for(long long i=headw[flag];i;i=ee[i].nxt)
            if(dis[ee[i].to]>dis[flag]+1)
            {
                dis[ee[i].to]=dis[flag]+1;
                ans[ee[i].to]=ans[flag];
                if(!spfa[ee[i].to])
                {
                    q.push(ee[i].to);
                    spfa[ee[i].to]=1;
                }
            }
            else if(dis[ee[i].to]==dis[flag]+1)ans[ee[i].to]+=ans[flag];
    }
}
void dfs1(long long x)
{
    vis1[x]=1;
    for(long long i=head[x];i;i=e[i].nxt)
    {
        if(!vis1[e[i].to])
        {
            if(e[i].w)que[++que[0]]=e[i].to;
            else dfs1(e[i].to);
        }
    }
}
void dfs2(long long x)
{
    vis2[x]=1;
    for(long long i=1;i<=que[0];i++)
        if(!con[x][que[i]])
        {
            h[x][++h[x][0]]=que[i];
            con[x][que[i]]=1;
        }
    for(long long i=head[x];i;i=e[i].nxt)
        if(!e[i].w&&!vis2[e[i].to])
            dfs2(e[i].to);
}
int main()
{
    for(long long i=1;i<=3000;i++)dis[i]=20020923002002092300;
    scanf("%lld%lld",&n,&m);
    for(long long i=1;i<=n;i++)
        for(long long j=1;j<=m;j++)
        {
            scanf("%lld",&Map[i][j]);
            wzc[i][j]=++wzc[0][0];
            if(Map[i][j]==3)
            {
                st=wzc[i][j];
                Map[i][j]=0;
            }
            if(Map[i][j]==4)
            {
                ed=wzc[i][j];
                Map[i][j]=0;
            }
        }
    for(long long i=1;i<=n;i++)
        for(long long j=1;j<=m;j++)
        {
            if(Map[i][j]==2)continue;
            if(i-1>0&&j-2>0&&Map[i-1][j-2]!=2)
            {
                if(Map[i-1][j-2])add(wzc[i][j],wzc[i-1][j-2],0);
                else add(wzc[i][j],wzc[i-1][j-2],1);
            }
            if(i-2>0&&j-1>0&&Map[i-2][j-1]!=2)
            {
                if(Map[i-2][j-1])add(wzc[i][j],wzc[i-2][j-1],0);
                else add(wzc[i][j],wzc[i-2][j-1],1);
            }
            if(i-1>0&&j+2<=m&&Map[i-1][j+2]!=2)
            {
                if(Map[i-1][j+2])add(wzc[i][j],wzc[i-1][j+2],0);
                else add(wzc[i][j],wzc[i-1][j+2],1);
            }
            if(i-2>0&&j+1<=m&&Map[i-2][j+1]!=2)
            {
                if(Map[i-2][j+1])add(wzc[i][j],wzc[i-2][j+1],0);
                else add(wzc[i][j],wzc[i-2][j+1],1);
            }
            if(i+1<=n&&j-2>0&&Map[i+1][j-2]!=2)
            {
                if(Map[i+1][j-2])add(wzc[i][j],wzc[i+1][j-2],0);
                else add(wzc[i][j],wzc[i+1][j-2],1);
            }
            if(i+2<=n&&j-1>0&&Map[i+2][j-1]!=2)
            {
                if(Map[i+2][j-1])add(wzc[i][j],wzc[i+2][j-1],0);
                else add(wzc[i][j],wzc[i+2][j-1],1);
            }
            if(i+1<=n&&j+2<=m&&Map[i+1][j+2]!=2)
            {
                if(Map[i+1][j+2])add(wzc[i][j],wzc[i+1][j+2],0);
                else add(wzc[i][j],wzc[i+1][j+2],1);
            }
            if(i+2<=n&&j+1<=m&&Map[i+2][j+1]!=2)
            {
                if(Map[i+2][j+1])add(wzc[i][j],wzc[i+2][j+1],0);
                else add(wzc[i][j],wzc[i+2][j+1],1);
            }
        }
    for(long long i=1;i<=n;i++)
        for(long long j=1;j<=m;j++)
            if(Map[i][j]==1&&!vis1[wzc[i][j]])
            {
                que[0]=0;
                dfs1(wzc[i][j]);
                memset(vis2,0,sizeof(vis2));
                dfs2(wzc[i][j]);
            }
    for(long long i=1;i<=n;i++)
        for(long long j=1;j<=m;j++)
        {
            if(Map[i][j]==2||Map[i][j]==1)continue;
            for(long long k=head[wzc[i][j]];k;k=e[k].nxt)
                if(e[k].w)
                {
                    if(!qj[wzc[i][j]][e[k].to])
                    {
                        qj[wzc[i][j]][e[k].to]=1;
                        add_w(wzc[i][j],e[k].to);
                    }
                }
                else
                {
                    for(long long l=1;l<=h[e[k].to][0];l++)
                        if(!con[wzc[i][j]][h[e[k].to][l]]&&!qj[wzc[i][j]][h[e[k].to][l]])
                        {
                            qj[wzc[i][j]][h[e[k].to][l]]=1;
                            add_w(wzc[i][j],h[e[k].to][l]);
                            con[wzc[i][j]][h[e[k].to][l]]=1;
                        }
                }
        }
    SPFA();
    cout<<dis[ed]-1<<endl<<ans[ed]<<endl;
    return 0;
}

例3代码转载自: ওHEOI-动动ও 。 (如有侵权,私信删除)

如有不足,请诸位大佬指教

原文地址:https://www.cnblogs.com/ssw02/p/11396556.html