最短路的拓展
- 在求多个点到一个点的距离时,我们可以将边反向,做一次最短路即可。
- 在写SPFA时,有一个简单的小优化,SLF优化,如果要进队的点的dist小于队首的dist,则将其加在队首 http://www.cnblogs.com/cj695/archive/2012/07/27/2611215.html
- 在判断是否存在负权环时,我们可以在SPFA中加入used[]数组,每当j被松弛一次,used[j]++,if used[j]>n ,那么存在负权环(如果不放心的话,可以写>n+1)
- 插句题外话,floyd多好用的
- 我们可以使用修改后的floyd求传递闭包(可以求解一些连通性问题,判定任意两点见是否有路;也可以用来求有向无环图的根->然而我觉得没有luan用)
for(int k=1;k<=n;k++)//枚举中间点
for(int i=1;i<=n;i++)//枚举起点
for(int j=1;j<=n;j++)//枚举终点
f[i][j]=f[i][j] ||(f[i][k] && f[k][j]);
- 可以使用floyd求有向和无向带权图的最小环问题
-
- 先来看有向图:
-
- 令f[i][i]=inf , 经过floyd后,求得的f[i][i]即为最小环长度
- 但是O(n^3)的时间复杂度总是不那么令人满意的
- 可以枚举起点s,新增点s',若存在边(u,s),则新增一条边(u,s'),求s到s'的最短路即可。所有最短路中最短的一条即是最小环,用SPFA或者heap+dijkstra均可通过。
- 用DFS求有向带权图图的最小环似乎也是可以的,枚举起点即可,加点剪枝,应该还是比floyd好。
- 那么无向图的最小环呢?
-
- 仔细观察Floyd的三层循环,发现中间点枚举的顺序是从小到大,也就是当枚举到某个点k,对所有小于它的点对i和j,从i到j的最短路是不经过k的,如果此时有边g[i,k]和g[k,j]则可形成一个k->i->....->j->k的环,再用k去松弛所有的点对,以便下次枚举k+1时重新找与1~k之间的点形成的环,找出所有环中最小的。g[][]表示原图中各点之间距离,dist[i][j]表示i,j之间最短距离
for(int k=1; k<= n; k++)
{
for(int i=1; i<=k-1; i++) //求最小环
for(int j=i+1; j<=k-1; j++)
if(dist[i][j]!=INF&&g[i][k]!=INF&&g[k][j]!=INF)
ans=min(ans,dist[i][j]+g[i][k]+g[k][j]);
for(int i=1; i<=n; i++) //求最短路
for(int j=1; j<=n; j++)
if(dis[i][k]!=INF&&dis[k][j]!=INF)
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
}
- 还可以使用floyd算法来求任意两点间的最短路径条数,g表示条数
if(d[i][j]==d[i][k]+d[k][j])g[i][j]+=g[i][k]*g[k][j];//相等
if(d[i][k]+d[k][j]<d[i][j])//修改
{
d[i][j]=d[i][k]+d[k][j];
g[i][j]=g[i][k]*g[k][j];
}
- 那么如果我们是求给定两点
间的最短路径条数,floyd就不太理想了,我们可以使用 -
- 用BFS实现——边权长度为1的情况,O(n);
- Dijkstra算法。时间复杂度O(n^2); 关键语句如下:
-
- if(d[j]==d[k]+a[k][j])g[j]+=g[k];
- if(d[j]>d[k]+a[k][j]){d[j]=d[k]+a[k][j];g[j]=g[k];}
- SPFA算法。时间复杂度O(2E);
-
- SPFA的处理要稍微复杂一些,如果我们还像上面那样,就会出错,因为SPFA会反复优化,一个点进队多次,如果直接累加的话肯定会加多
- 故用dlt[i]表示当前在队列中以元素i结尾的路径条数,每次要弹队首时(即这个点已经优化完了目前它所能优化的点),将dlt清零
vis[v0]=1;d[v0]=0;dlt[v0]=1;q.push(v0);
while(!q.empty())
{
int fr=q.front();
q.pop();vis[fr]=0;
for(int i=h[fr];i;i=w[i].nxt)
{
int j=w[i].to;
if(d[j]>=d[fr]+w[i].val)
{
if(d[j]==d[fr]+w[i].val)
{
g[j]+=dlt[fr];
dlt[j]+=dlt[fr];
}
else
{
d[j]=d[fr]+w[i].val;
g[j]=dlt[j]=dlt[fr];
}
if(!vis[j])
{
vis[j]=1;
q.push(j);
}
}
}
dlt[fr]=0;
}
- 求从S出发恰好经过K步(允许有重复边)到达E总共有多少走法该怎么做呢
-
- 把给定的图转为邻接矩阵,即A[i,j]=1当且仅当存在一条边i->j。令C=A*A,那么C[i,j]=ΣA[i,k]*A[k,j],实际上就等于从点i到点j恰好经过2条边的路径数(枚举k为中转点)。类似地,C*A的第i行第j列就表示从i到j经过3条边的路径数。同理,如果要求经过k步的路径数,我们只需要二分求出A^k即可。
- 求从S出发恰好经过K条边(允许有重复边)到达E的最短距离
-
- 可以看这么一道例题 Cow Relays(POJ3613)BSOJ3114
-
- 我们可以使用倍增+floyd(即用倍增法加速了状态转移)
- 但是在余华程的论文《矩阵乘法在信息学中的应用》有对这道题使用矩阵的详细论证
- 用修改的floyd算法求每两点间的k短路
-
- •设d[i][j][L]为ij,只经过1..L的前k短路长度(向量)
- •d[i][j][L] =Min{d[i][j][L-1],d[i][L][L-1] +d[L][L][L-1]* +d[L][j][L-1]
- •两个k维向量A, B的运算
-
- Min{A,B}:A和B共2k个数取前k个O(K)
- A+B:A[i]+B[j]共k2个和取前k个O(KlogK)
- A*=Min{0, A, 2A, 3A, …} O(K2logK)
- •计算d[L][L][L-1]*的时间复杂度:O(nK2logK)
- •计算状态d[i][j][L]: O(2KlogK+K)=O(KlogK)
- •总时间复杂度:O(nK2logK+KlogKn3)
- Floyd是一个优秀的算法,通过N次枚举中间点,将点i,j之间的距离松弛为最短。
- 求从S到E第K短路(允许重复经过边)长度
-
- 长度相同的路算同一条利用类AStar思想,以当前权值加上点到终点E的距离和(即在原图的逆图中跑SPFA先求出E到各点的距离)作为估价函数,用以决定其出队列的优先级别,当终点E出队列的距离和上次的不同,则计数器cnt++,若cnt==K则返回当前权值,一个剪枝是将cnt修改为cnt[] 同时记录其他各点出队列的有效次数(权值和上次相等的视为无效),若超过k,则不再放入队列,因为每个点的前面k个值足以形成从S到E的第K短路。
- 求第二短路问题
-
- 用dijkstra找出一条最短路径p1。
- 由于第二最短路径至少有一条边和p1不同,所以可以这样求第二短路:枚举p1中的每一条边e1,将e1从图G中删除得到G’,然后再G’中求最短路,所有这些G’最短路中最短的为第二短路p2。
- 注意:上述方法是边不能够重复走的次短路
- 那么如果可以重复走呢?
-
- 方法为先用Heap+Dijkstra(SPFA)求出1和N的单源最短路径,把无向边看成两个有向边,然后枚举每单向条边(u,v),计算Dist=dis(1,u) + dis(N,v)+map[u][v],看看此时Dist的值是否大于dis(1,N),如果是的话用它更新次短路径,保留一个最小的值。
- 求a—b的最短路径经过C点的路径条数
-
- 分别从A和C两点求单源最短路径;
- 若A到B的最短路径=A到C的最短路径+C到B的最短路径,则ANS=g[A][C]*g[C][B]。
- 路径减半问题
-
- 从1开始Dijkstra(1,1),保存在d[1][i]中;
- 从n开始Dijkstra(n,2),保存在d[2][i]中;
- 枚举每条边(i,j),将其边权减少一半,看d[1][i]+w[i][j]/2+d[j][n]是否最小的。注意边是无向的,应该两个方向都要枚举。
- 如果k次路径减半呢(可以作用于同一条边)
-
- 分层图啊,少年
- 拆点啊
- 在一个给定的有向无环图中,求从开始顶点到结束顶点的最长路径(路径上的权值和)叫关键路径。
-
- 大家都说拓扑排序后加DP
- 但是我觉得做一次SPFA也挺好的
- 对于有向图点权转边权问题
-
- 图(a)是一个包含4个带权点的有向图。要把点权变成边权,首先将图中所有的边的权值设为0,然后将每个顶点拆成两个点vi和vi’,并添加一条有向边<vi’,vi>,权值为原顶点的权值。对于原图中的边,所有终点为点i的边在新图中连到vi’上,所有起点为i的边在新图中连到vi上,对于图(a)进行上述转化后变为图(b)。对于图(b),会发现所有的边<vi,vj’>都是0,由于对于每个点j,都有一条<vj’,vi>,现在把所有的点vj’拿掉,对于每条<vi,vj’>,新生成一条边<vi,vj>,权值等于<vi,vj’>+<vj’,vj>=0+<vj’,vj>=<vj’,vj>,由于vi’没有入边,所以暂时保留,但是更名为v0。对于图(b)进行如此操作后,得到图(c)。现在对比图(a)和图(c),可以发现,将点权转化为边权,就是直接对于每条边赋权值,该权值等于该边终点的点权,没有入边的点需要添加一个节点,并连接一条有向边,使之边权等于该点点权,对于图(c)来说,v0就是为了处理点v1而专门添加的点。如果有多个没有入边的点,只需要添加一个顶点,使之与所有没有入边的顶点相连即可。
- 对于边权化点权的问题,我们不妨来看下面这一道题。
3800 -- 【四校联考1】染色
Description
人人生而平等,然而后来人们逐渐选择了不同的道路,人与人之间,变得不再平等。你可能会因为染上恶人之色而堕落,成为社会的败类;你也可能因为染上善人之色,而在逆境中不断成长。但是,红与黑并非绝对的。所谓“近朱者赤,近墨者黑”,你能改变周遭的环境,那么周遭的环境也会影响你。在黑暗的人群中,善人之色是明亮的,而在明亮的人群中,善人之色的存在就会显得微薄。你的颜色,依靠着你自己的意志在选择。
如今,你获得了一次重生,你要为自己,以及你周围的人们,重新染上一种你真正想要的颜色。具体来说,你的圈子里总共有N个人,每个人你可以选择为其重新染色,或者不染。对于第i个人,如果他/她被重新染色,那么这将会对社会带来wi的改变。同时,朋友关系也会对社会带来改变,如果i,j是朋友,那么他/她们会对社会带来的改变w(i,j)满足
Input
Output
输出仅一行为一个整数,对社会产生的总的改变的最大值。
Hint
【数据规模与约定】
对于40%的数据,M=0。
对于另外40% 的数据,N,M≤20。
对于所有数据,N,M≤10^5。
第一次做的时候我就蠢了,最后20%妄图用分层图做,真是。。。
其实由于这道题的特殊性,我们可以将边权加在点权上,那么再让ans-边权,最后再累加所有大于0的点权'
对于这个贪心的正确性
我们可以分类讨论以下情况: -
- 两个点都选,减去多加的一个边权正好
- 两个点都不选,那么我们根据题意减去边权也是正确的
- 如果选择了其中一个点,那么由于不应加边权,所以减去一个边权也是正确的。
- 实在是妙,但是这不是典型的,有一道典型的边权化点权我记不起来了。。。
- 大致上有套路的最短路差不多就是这些,但是那有很多需要转化的,就下次再写了