• byhttps://blog.csdn.net/zuzhiang/article/details/78154934
  •  题意
    • 给出有n个点,m条边的图,每个点有一个值(点权),走到这个点就可以得到这个值。其中入度为0的点为源点,出度为0的点为汇点,源点和汇点可能有多个,问从源点走到汇点可以得到的最大值是多少。
  • 题解
    • 前置技能
      1. 链式前向星,这是存储图的一种方式,对于复杂图来说可以说是最好的存储方式,没有之一。
      2. DAG上求单源最短路径算法,具体思想就是先得到DAG图的拓扑序列,然后按照拓扑排序的顺序处理每个点(不论边权正负)
      3. 超级源点,重新构图的一种方式
    • 我们注意到,n和m的值都很大(1 ≤ n ≤ 100000, 0 ≤m ≤ 1000000),直接用无向图的求最短路算法肯定会超时,又注意到我们能构造的图其实是个有向无环图,所以就可以用求DAG图上的单源最短路径算法来解决了,其时间复杂度为O(m)。
    • 但是题目中明明说了可能有多个源点,并且告诉我们的不是边权而是点权。所以我们就得自己转换一下思路,重新构图。
    • 上图是根据题目给出的数据形成的图,可以很明显的得出结果为7。下面我们来重新构造一下图
    • 首先,我们加入一个超级源点(0点)和超级汇点(n+1点),让超级源点连接原来的所有源点,让原来的所有汇点连接超级汇点。然后还需要把点权转化为边权,具体做法是点 v 的点权转化为以 v 为入点的边上的边权,至于点到超级汇点之间的边权设为0。这样从某个源点走到某个汇点的最大距离就是从超级源点走到超级汇点的最大距离了。
    • 具体实现:前面提到过,由于数据量很大,应该选用链式前向星的方式存储图结构,vector理论上也可以,但是肯定没有链式前向星好。首先根据给出的边和点权构图,将超级源点连接原来的所有源点,将原来的所有汇点连接超级汇点。接着跑一边拓扑排序,然后再根据拓扑序列求得最短路。
    • 注意
      • 1.数据很大,结果应该用long long型存储。
      • 2.求的是最大距离,所以要在最短路的基础上改一下。
      • 3.传统的拓扑排序算法时间复杂度为O(n^2)会超时,我这里用了队列来优化,因为一个节点只有在删除与它相连的边的时候它的入度才可能变为0,只判断这些点并将入度为0的点入队会节省很多时间。
  • 代码
    • 
      #include<stdio.h>
      #include<string.h>
      #include<iostream>
      #include<queue>
      #define inf 0x3f3f3f3f
      using namespace std;
      typedef long long LL;
       
      int n,m;
      int tol,cnt,head[100010]; //链式前向星 
      //w记录点权,chu记录点的出度,ru记录点的入度,que记录拓扑序列 
      int w[100010],chu[100010],ru[100010],que[100010];
      //dis记录点到源点的最大距离 
      LL dis[100010];
      queue<int> Q;
       
      struct node
      { //链式前向星 
      	int w,to,next; //分别为边权、终点和以u为起点的下一条边在的位置 
      } edge[2000010];
       
      void init()
      { //初始化函数 
      	tol=0;
      	memset(head,-1,sizeof(head));
      	memset(chu,0,sizeof(chu));
      	memset(ru,0,sizeof(ru));
      }
       
      void add(int u,int v,int w)
      { //加边函数 
      	edge[tol].w=w;
      	edge[tol].to=v;
      	edge[tol].next=head[u];
      	head[u]=tol++;
      }
       
      void tuopu()
      { //获得拓扑排序 
      	int i,j,top;
      	cnt=0;
      	dis[0]=0;  
      	Q.push(0); //0点入队
      	for(i=1;i<=n+1;i++) dis[i]=-inf; //初始化最长距离为最小值 
      	while(!Q.empty())
      	{ //只有减边的点才有可能入度为0 
      		top=Q.front();
      		Q.pop();
      		que[cnt++]=top; //保存拓扑序列 
      		ru[top]--;      //当前节点入度-1
      		for(j=head[top];j!=-1;j=edge[j].next)
      		{ //遍历与top相连的节点 
      			ru[edge[j].to]--; //入度-1 
      			if(ru[edge[j].to]==0) Q.push(edge[j].to); //如果入度为0则入队 
      		}
      	}
      }
       
      void solve()
      {
      	int i,j,v;
      	for(i=0;i<cnt;i++) //依照拓扑序列处理 
      		for(j=head[que[i]];j!=-1;j=edge[j].next)
      		{ //遍历i的相邻节点 
      			v=edge[j].to;
      			if(dis[v]<dis[que[i]]+edge[j].w) //更新距离 
      				dis[v]=dis[que[i]]+edge[j].w;
      		}
      	printf("%lld\n",dis[n+1]);
      }
       
      int main()
      {
      	int i,j,u,v;
      	while(~scanf("%d%d",&n,&m))
      	{ //点数和边数 
      		init();
      		for(i=1;i<=n;i++) scanf("%d",&w[i]); //输入点权 
      		for(i=0;i<m;i++)
      		{
      			scanf("%d%d",&u,&v);
      			add(u,v,w[v]);
      			chu[u]++;
      			ru[v]++;
      		}
      		for(i=1;i<=n;i++)
      		{
      			if(ru[i]==0)
      			{ //在0点和入度为0的点之间加边 
      				add(0,i,w[i]);				
      				ru[i]++;
      			}
      			if(chu[i]==0)
      			{ //在出度为0的点和点n+1之间加边 
      				add(i,n+1,0);
      				ru[n+1]++;
      			}
      		}
      		tuopu();
      		solve();
      	}
      	return 0;
      }
      

发表评论

邮箱地址不会被公开。 必填项已用*标注