题干:
题目大意:
一个有向图,编号1~n的n个点,m条边,规定1为起点,2为终点,问对于每一条边,反转它的方向,最短路会不会发生改变,如果变短了,输出HAPPY,变长了或者到达不了了输出SAD,不变的话输出SOSO。
解题报告:
建三个图,正向图G1,反向图G2,对于G1的边(u,v),假如G1.d[u]+G2.[v]+w==G1.d[2],那么他就是最短路上的一条路径,用这些路径重新建一个图G3,那么显然这个G3中任意一条点1到点2的路径都是最短路,对G3跑一遍tarjan求个桥,标记处理一下对应G1的哪条边,记下编号。然后枚举G1的每一条边,如果G1.d[v]+G2.[u]+w==G1.d[2],那么就是HAPPY,否则分两种情况:如果这条边是桥,那么改变它的方向是会改变新图的连通性的,必定不能到达2点,输出SAD;如果不是桥,那么就是SOSO,因为有别的方式可以到达点2。
AC代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
const int MAX = 2e5 + 5;
struct Edge {int fr,to;ll w;int ne;
// Edge(int fr=0,int to=0,ll w=0,int ne=-1):fr(fr)
};
struct Graph {struct Point {int pos;ll c;Point(){}Point(int pos,ll c):pos(pos),c(c){}bool operator<(const Point b) const {return c > b.c;}};Edge e[MAX];int tot = 0;//总共tot条边,编号0~(tot-1) int head[MAX],id[MAX];bool vis[MAX];ll d[MAX];void add(int u,int v,ll w) {e[tot].fr = u;e[tot].to = v;e[tot].ne = head[u];e[tot].w = w;head[u] = tot++; }void Dijkstra(int st) {priority_queue<Point> pq;memset(d,0x3f,sizeof d);memset(vis,0,sizeof vis);d[st] = 0;pq.push(Point(st,0));while(pq.size()) {Point cur = pq.top();pq.pop();if(vis[cur.pos]) continue;vis[cur.pos] = 1; for(int i = head[cur.pos]; ~i; i = e[i].ne) {if(d[e[i].to] > d[cur.pos] + e[i].w) {d[e[i].to] = d[cur.pos] + e[i].w;pq.push(Point(e[i].to,d[e[i].to]));}}}}///int clk = 0;int DFN[MAX],LOW[MAX],bi[MAX];void id_add(int u,int v,ll w,int _id) {e[tot].fr = u;e[tot].to = v;e[tot].ne = head[u];e[tot].w = w;id[tot] = _id;//记录编号为tot的这条边是第几个点的 head[u] = tot++; }void tarjan(int x,int rt) {DFN[x] = LOW[x] = ++clk;for(int i = head[x]; ~i; i = e[i].ne) {if(e[i].to == rt) continue; if(!DFN[e[i].to]) {tarjan(e[i].to,x);LOW[x] = min(LOW[x],LOW[e[i].to]);if(LOW[e[i].to] > DFN[x]) {
// printf("%d**%d\n",e[i].fr,e[i].to);bi[id[i]] = 1;}}else LOW[x] = min(LOW[x],DFN[e[i].to]);
//或者不用判重边了直接用这一句
// else if(e[i].to != rt)LOW[x] = min(LOW[x],DFN[e[i].to]);
//而且其实不应该是直接用这一句 而应该是传参的时候传入边的编号rt,这样我们else if(i != (rt^1))这样才对,因为这代表的才是,不是祖先边的回边。所以这里严格来说不能直接判断是否是祖先节点,因为还有可能有重边或者自环的情况,但是这题因为数据保证了所以无所谓。 }}void init() {tot = 0;clk = 0;memset(head,-1,sizeof head);}
} G1,G2,G3;
int main()
{int n,m;int u,v;ll w;cin>>n>>m;G1.init();G2.init();G3.init();//G1是原图,G2是反图,G3是最短路无向子图 for(int i = 1; i<=m; i++) {scanf("%d%d%lld",&u,&v,&w);G1.add(u,v,w);G2.add(v,u,w);//加反边 }G1.Dijkstra(1);G2.Dijkstra(2);ll DIS = G1.d[2];
//构造G3这个最短路子图。 for(int i = 0; i<G1.tot; i++) {u = G1.e[i].fr;v = G1.e[i].to;w = G1.e[i].w;if(G1.d[u] + G2.d[v] + w == DIS) {
// printf("%d@@@@@@%d\n",u,v);G3.id_add(u,v,w,i);G3.id_add(v,u,w,i);}}G3.tarjan(1,-1);//改成0也可以过。for(int i = 0; i<G1.tot; i++) {u = G1.e[i].fr;v = G1.e[i].to;w = G1.e[i].w;if(G1.d[v] + G2.d[u] + w < DIS) puts("HAPPY");else if(G3.bi[i]) puts("SAD");else puts("SOSO");} return 0 ;
}