题目大意
给出一个有向图,给出起点和终点,问每条边,是否图中存在的每一条起点到终点的最短路径都经过它(条件YES),如果存在不经过它的最短路径,可以减小它的多少边权(减少量不得超过该边原来的边权)使得该边满足条件YES。
题解
我们规定图中起点到终点的所有最短路径所经过的点、边构成的子图叫做最短路径子图。判断一条边是否在最短路径子图中很容易,从起点来一遍Dijkstra得到每个节点到起点的最短距离v->DistS,从终点来一遍Dijkstra得到每个节点到终点的距离v->DistT。如果边e->From->DistS + e + e->To->DistT==最短路径长度,则边e在最短路径子图中。
问题就卡在一条在最短路径子图中的边是否满足条件YES。我曾经想通过类似于Bfs的方式,运用优先队列,key值边权,值是边的指针。但是这样做会出现各种各样的问题。所以以后记住,自己发明的Bfs、Dfs算法往往都不对,要是考试,不要在这个方面上抠!
我们发现,如果将最短路径子图变成无向图,一条在最短路径子图上的边在一个点双连通分量中与该边不满足YES是等价命题。所以当时我想:我们把最短路径子图上的点双连通分量全求出来,那么其它的边就都满足YES了。但是求点双连通分量细节多多,麻烦。所以我们要反着想:如果一条边满足条件YES,也就是这条边不在点双连通分量中,那么这条边是什么?割边嘛!所以在子图上Tarjan即可。
PS:感谢CodeForce,以前写Dijkstra时,习惯于在优先队列里维护节点指针,key值为节点指针对应的节点的Dist。然而这样是错的,因为一个节点在操作过程中,它Dist会被改掉,这样堆的key值就乱了。这个错误方法曾经在无数洛谷题中屡试不爽,这次让我发现了问题。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define Pair pair<long long, Node*>
const int MAX_NODE = 200010, MAX_EDGE = 400010;
const long long INF = 1e17;
struct Node;
struct Edge;
struct Node
{
Edge *HeadS, *HeadT, *Head;
long long Dist, DistT;
bool Done;
int Low, DfsN;
}_nodes[MAX_NODE], *Start, *Target;
int TotNode;
struct Edge
{
Node *To;
Edge *Next, *Rev;
long long Weight;
bool InSubG;//InSubGraph
bool IsCut;
}_edges[MAX_EDGE];
int TotEdge;
void AddEdge(int uId, int vId, int eId, int w)
{
Node *from = _nodes + uId, *to = _nodes + vId;
Edge *e = _edges + eId, *revE = _edges + eId + TotEdge;
e->To = to;
e->Weight = w;
e->Next = from->HeadS;
from->HeadS = e;
revE->To = from;
revE->Weight = w;
revE->Next = to->HeadT;
to->HeadT = revE;
e->Rev = revE;
revE->Rev = e;
}
struct HeapNode
{
long long Dist;
Node *CurNode;
HeapNode(long long dist, Node *curNode):Dist(dist), CurNode(curNode){}
bool operator < (const HeapNode& a) const
{
return Dist > a.Dist;
}
};
void Dijkstra(Node *start)
{
for (int i = 1; i <= TotNode; i++)
{
_nodes[i].Dist = INF;
_nodes[i].Done = false;
}
start->Dist = 0;
static priority_queue<HeapNode> q;
q.push(HeapNode(0, start));
while (!q.empty())
{
HeapNode temp = q.top();
q.pop();
Node *cur = temp.CurNode;
if (cur->Done)
continue;
cur->Done = true;
for (Edge *e = cur->Head; e; e = e->Next)
{
if (cur->Dist + e->Weight < e->To->Dist)
{
e->To->Dist = cur->Dist + e->Weight;
q.push(HeapNode(e->To->Dist, e->To));
}
}
}
}
void MinDist_T()//getMinDistFromTargetNode
{
for (int i = 1; i <= TotNode; i++)
_nodes[i].Head = _nodes[i].HeadT;
Dijkstra(Target);
}
void MinDist_S()//getMinDistFromStartNode
{
for (int i = 1; i <= TotNode; i++)
{
_nodes[i].DistT = _nodes[i].Dist;
_nodes[i].Head = _nodes[i].HeadS;
}
Dijkstra(Start);
}
void GetSubGraph()
{
for (int i = 1; i <= TotEdge; i++)
if (_edges[i].Rev->To->Dist + _edges[i].Weight + _edges[i].To->DistT == Target->Dist)
_edges[i].InSubG = _edges[i].Rev->InSubG = true;
}
void CombineGraph()
{
for (int i = 1; i <= TotNode; i++)
{
_nodes[i].Head = _nodes[i].HeadS;
Edge **e = &_nodes[i].Head;
while (*e)
e = &(*e)->Next;
*e = _nodes[i].HeadT;
}
}
int DfsCnt;
void Dfs(Node *cur, Edge *from)
{
cur->Low = cur->DfsN = ++DfsCnt;
for (Edge *e = cur->Head; e; e = e->Next)
{
if (!e->InSubG)
continue;
if (!e->To->DfsN)
{
Dfs(e->To, e);
cur->Low = min(cur->Low, e->To->Low);
if (cur->DfsN < e->To->Low)
e->IsCut = e->Rev->IsCut = true;
}
else if (e->Rev != from)
cur->Low = min(cur->Low, e->To->DfsN);
}
}
void SolveEdge(Edge *e)
{
if (e->IsCut)
{
printf("YES\n");
return;
}
if (e->Rev->To->Dist == INF || e->To->DistT == INF)
{
printf("NO\n");
return;
}
long long delta = e->Rev->To->Dist - Target->Dist + e->Weight + e->To->DistT + 1;
if (delta >= e->Weight)
printf("NO\n");
else
printf("CAN %lld\n", delta);
}
int main()
{
int s, t;
scanf("%d%d%d%d", &TotNode, &TotEdge, &s, &t);
Start = _nodes + s;
Target = _nodes + t;
for (int i = 1; i <= TotEdge; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
AddEdge(u, v, i, w);
}
MinDist_T();
MinDist_S();
GetSubGraph();
CombineGraph();
Dfs(Start, NULL);
for (int i = 1; i <= TotEdge; i++)
SolveEdge(_edges + i);
return 0;
}