解题报告
题目链接:https://www.luogu.com.cn/problem/P3008
分析
上来一看就是最短路问题。
首先,这题可能因为数据比较老的原因用SLF优化的SPFA可以过掉(但洛谷会卡到90分),我一开始就是这么水过的...SLF优化就是把普通SPFA的queue队列改成deque双端队列,在入队时跟目前的队首比较一下,如果比队首小就从前方入列,比队首大就从后方入列,利用的就是先扩展最小的点可以尽量减少我们之后松弛的操作次数,然鹅这个优化本身就跟SPFA一样有点玄学...他的时间效率也是不能保证的,只能说大部分时候是有点用的(笑)。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<deque>
using namespace std;
const int maxn=250000+1;
#define Dio ios::sync_with_stdio(0)
#define ll long long
int m1,m2,n,t,S;
int ans=0;
int head[maxn],len=0;
int dis[maxn];
struct Edge{
int to,next,dis;
}edge[maxn];
void Add(int u,int v,int w){
edge[++len].next=head[u];
edge[len].to=v;
edge[len].dis=w;
head[u]=len;
}
bool vis[maxn];
//int cnt[maxn];
deque<int> q;
void Spfa(){
for(int i=1;i<=n;i++)
dis[i]=0x3f3f3f3f;
dis[S]=0;
vis[S]=1;
q.push_back(S);
while(!q.empty()){
//cout<<1<<endl;
int x=q.front();
q.pop_front();
vis[x]=0;
//cnt[x]++;
for(int i=head[x];i;i=edge[i].next){
//cout<<1<<endl;
int v=edge[i].to;
//if(cnt[v]>n) continue ;
if(dis[v]>dis[x]+edge[i].dis){
dis[v]=dis[x]+edge[i].dis;
if(!vis[v]){
vis[v]=1;
if(dis[v]<dis[q.front()]) q.push_front(v);
else q.push_back(v);
}
}
}
}
}
int main(){
Dio;
cin>>n>>m1>>m2>>S;
for(int i=1;i<=m1;i++){
int u,v,w;
cin>>u>>v>>w;
Add(u,v,w),Add(v,u,w);
}
for(int i=1;i<=m2;i++){
int u,v,w;
cin>>u>>v>>w;
Add(u,v,w);
}
Spfa();
for(int i=1;i<=n;i++){
if(dis[i]==0x3f3f3f3f)cout<<"NO PATH"<<endl;
else cout<<dis[i]<<endl;
}
return 0;
}
然后是正解:
对于整张图,边权有正有负,显然直接一波dij莽上去是不行的。但我们注意到,对于每一条道路,即双向边,边权都是正的,只有单向边可能带负权,而且保证不能让我们从后面的连通块跑回来。这就很友好了,一条条单向边相当于把整张图分成一个个的连通块(内部都是双向边),把每个连通块看作一个点,把单向边看作边,那么整张图是不是变成了一张DAG?在DAG上我们无论权正负都可以考虑按拓扑序进行扫描,求解最短路,套用到这道题上也是适用的。再一步就是处理每个连通块内部时,因为都是正权,直接Dij即可。
然鹅即使这样在洛谷上依然有一个点会超时,我直接把蓝书上的代码粘过去也是一样...目前还在想解决的办法(难受)。
然后问了问别人都是怎么过的,结果答案是吸氧...
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<deque>
#include<queue>
#define JoJo ios::sync_with_stdio(0)
using namespace std;
const int maxn=250000+1,maxm=1500000+1,inf=0x3f3f3f3f;
int T,R,P,S;
int head[maxn],len=0;
struct Edge{
int to,next,dis;
}edge[maxn<<1];
void Add(int u,int v,int w){
edge[++len].to=v;
edge[len].next=head[u];
edge[len].dis=w;
head[u]=len;
}
int scc_cnt=0;
int belong[maxn],dis[maxn],deg[maxn];
//belong[x]为x所属的连通块编号
//deg[x]为第x个连通块的总入度
void Dfs(int u){
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(!belong[v]){
belong[v]=scc_cnt;
Dfs(v);
}
}
}
bool vis[maxn];
queue<int> q;//普通队列用于保存连通块
priority_queue<pair<int ,int> > Q;//优先队列用于跑Dij
void Dij(){
while(!q.empty()){
int x=q.front();q.pop();
for(int i=1;i<=T;i++)
if(belong[i]==x) Q.push(make_pair(-dis[i],i));
while(!Q.empty()){
int t=Q.top().second;
Q.pop();
if(vis[t]) continue;
vis[t]=1;
for(int i=head[t];i;i=edge[i].next){
int v=edge[i].to;
if(dis[v]>dis[t]+edge[i].dis){
dis[v]=edge[i].dis+dis[t];
if(belong[t]==belong[v])Q.push(make_pair(-dis[v],v));//保证在同一个连通块内
}
if(belong[t]!=belong[v]&&!--deg[belong[v]]) q.push(belong[v]);//如果下一点不在该连通块内,把下一点所在的连通块入度--,对于每一个连通块,我们只需要找一个进入点
}
}
}
}
int main(){
JoJo;
cin>>T>>R>>P>>S;
memset(dis,0x7f,sizeof(dis));
for(int i=1;i<=R;i++){
int x,y,z;
cin>>x>>y>>z;
Add(x,y,z);
Add(y,x,z);
}
for(int i=1;i<=T;i++){
if(!belong[i]){
belong[i]=++scc_cnt;
Dfs(i);
}
}
for(int i=1;i<=P;i++){
int x,y,z;
cin>>x>>y>>z;
Add(x,y,z);
deg[belong[y]]++;//记录连通块入度
}
q.push(belong[S]);
for(int i=1;i<=scc_cnt;i++)
if(!deg[i]) q.push(i);
dis[S]=0;
Dij();
for(int i=1;i<=T;i++){
if(dis[i]>inf)cout<<"NO PATH"<<endl;
else cout<<dis[i]<<endl;
}
return 0;
}