思路分析:
解法一:
由于这道题提到可能有负权边,那么dijsktra算法肯定首先就被我们排除了,floyd?想都不要想,最后我们只剩下了Spfa,但众所周知,USACO是卡Spfa的,但似乎没有其他的解法了,于是我们就可以像一道最短路板子题一样建图,从起点出发,跑一遍Spfa,交上去,果不其然,TLE掉了两个点。但也只有两个点,于是我们想到用SLF去优化它,所谓SLF,就是在Spfa入队时拿将要入队的数和队首元素比较,如果比队首元素小则插入队首,否则插在対尾,于是用到双端队列,每次入队前判断一下就好了。这样我们就可以在开O2的前提下水过这道题了,但不开O2还是会TLE掉一个点,其实这道题只是不想卡SLF,但SLF优化并没有基于复杂度,实际上可以构造数据来卡到2^n,甚至可能比普通spfa更慢(以上一句话来自洛谷大佬(id:1010_)),但既然能过,也是一种方法。
上代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 using namespace std; 6 #define debug printf("------------ "); 7 const int N=1e6+10; 8 int Head[N],tot,T,P,R,S,dis[N],vis[N]; 9 struct Node{ 10 int next,to,dis; 11 }edge[N]; 12 void Add(int x,int y,int z){ 13 edge[++tot].to=y; 14 edge[tot].next=Head[x]; 15 edge[tot].dis=z; 16 Head[x]=tot; 17 } 18 void Spfa(int x){ 19 memset(dis,0x3f,sizeof(dis)); 20 memset(vis,0,sizeof(vis)); 21 deque<int>q; //只有这里不同,其他都是板子 22 q.push_back(x);vis[x]=1;dis[x]=0; 23 while(!q.empty()){ 24 int u=q.front();q.pop_front(); 25 for(int i=Head[u];i;i=edge[i].next){ 26 int v=edge[i].to;vis[u]=0; 27 if(dis[v]>dis[u]+edge[i].dis){ 28 dis[v]=dis[u]+edge[i].dis; 29 if(!vis[v]){ 30 if(!q.empty()&&dis[v]>=dis[q.front()]) 31 q.push_back(v); 32 else q.push_front(v); 33 vis[v]=1; 34 } 35 } 36 } 37 } 38 } 39 int main(){ 40 // freopen("a.txt","r",stdin); 41 // freopen("my.txt","w",stdout); 42 scanf("%d%d%d%d",&T,&R,&P,&S); 43 for(int i=1;i<=R;++i){ 44 int x,y,z; 45 scanf("%d%d%d",&x,&y,&z); 46 Add(x,y,z);Add(y,x,z); 47 } 48 for(int i=1;i<=P;++i){ 49 int x,y,z; 50 scanf("%d%d%d",&x,&y,&z); 51 Add(x,y,z); 52 } 53 Spfa(S); 54 for(int i=1;i<=T;++i){ 55 if(dis[i]==0x3f3f3f3f) 56 printf("NO PATH "); 57 else printf("%d ",dis[i]); 58 } 59 return 0; 60 }
解法二:
虽然这道题明确说了会有负权,但那只是单向航道,双向的道路的权值仍然都是正值,所以在双向的道路内是符合dijsktra的使用前提的,于是我们想到缩点,我们可以把所有权值为正且相邻的点块缩在一起,这样就形成了一个DAG之后用拓扑排序在DAG上求解即可。需要注意的是,需要将不能互通的两点间距离初始化为一个很大的数,并在拓扑时要将所有入度为0的点算进去,防止出现不互达的情况。有了思路代码就不难写了,这样写能比解法一的时间快4倍左右。
向量是真的难用调的我都要吐了
上代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<queue> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 using namespace std; 7 const int N=1e6+10; 8 int T,R,P,S; 9 int Head[N],tot,indeg[N]; //indeg表示节点入度个数 10 struct Node{ 11 int next,to,dis; 12 bool c; //c用来表示该边是双向路径还是单向航道 13 }edge[N]; 14 vector<int>ve[N]; //存储缩点后新节点编号所包含的节点编号 15 void Add(int x,int y,int z,int c){ 16 edge[++tot].to=y; 17 edge[tot].next=Head[x]; 18 edge[tot].dis=z; 19 edge[tot].c=c; 20 Head[x]=tot; 21 } 22 int vis[N],belong[N],sum,dis[N]; 23 void dfs(int u,int num){ //缩点 24 vis[u]=1; 25 ve[num].push_back(u); 26 belong[u]=num; 27 for(int i=Head[u];i;i=edge[i].next){ 28 int v=edge[i].to; 29 if(!edge[i].c) continue; 30 if(!vis[v]) dfs(v,num); 31 } 32 } 33 struct Edge{ 34 int num,dis; 35 Edge(int a,int b){ 36 num=a;dis=b; 37 } 38 bool operator < (const Edge& a)const{ 39 return a.dis<dis; 40 } 41 }; 42 priority_queue<Edge>q; //用于dijsktra计算 43 queue<int>Q; //用于计算拓扑 44 void Topo_dijs(int x){ //拓扑和dijsktra结合 45 memset(dis,0x3f,sizeof(dis)); 46 memset(vis,0,sizeof(vis)); 47 for(int i=1;i<=sum;++i){ 48 if(!indeg[i]) Q.push(i); //所有入度为0的点进行拓扑 49 } 50 dis[x]=0; 51 while(!Q.empty()){ //拓扑更新联通块 52 int k=Q.front();Q.pop(); 53 for(int i=0;i<ve[k].size();++i) 54 if(dis[ve[k][i]]<0x3f3f3f3f) 55 q.push(Edge(ve[k][i],dis[ve[k][i]])); //找起点 56 while(!q.empty()){ //对联通块内节点dijsktra 57 Edge u=q.top();q.pop(); 58 //printf("%d ",u.dis); 59 if(vis[u.num]) continue; 60 vis[u.num]=1; 61 for(int i=Head[u.num];i;i=edge[i].next){ 62 int v=edge[i].to; 63 if(edge[i].c&&dis[v]>dis[u.num]+edge[i].dis){ 64 dis[v]=dis[u.num]+edge[i].dis; 65 q.push(Edge(v,dis[v])); 66 } 67 else if(!edge[i].c){ //遍历到另一个联通块更新即可,无须入队 68 dis[v]=min(dis[v],dis[u.num]+edge[i].dis); 69 } 70 } 71 } 72 for(int j=0;j<ve[k].size();++j) //更新下一个联通块 73 for(int i=Head[ve[k][j]];i;i=edge[i].next){ 74 int v=edge[i].to; 75 if(edge[i].c) continue; 76 if(--indeg[belong[v]]==0) 77 Q.push(belong[v]); 78 } 79 } 80 } 81 int main(){ 82 scanf("%d%d%d%d",&T,&R,&P,&S); 83 for(int i=1;i<=R;++i){ 84 int x,y,z; 85 scanf("%d%d%d",&x,&y,&z); 86 Add(x,y,z,1);Add(y,x,z,1); 87 } 88 for(int i=1;i<=P;++i){ 89 int x,y,z; 90 scanf("%d%d%d",&x,&y,&z); 91 Add(x,y,z,0); 92 } 93 for(int i=1;i<=T;++i){ //割点 94 if(!vis[i]) dfs(i,++sum); 95 } 96 for(int j=1;j<=T;++j) 97 for(int i=Head[j];i;i=edge[i].next){ 98 if(edge[i].c) continue; //是缩点后的拓扑故双向路径不算 99 indeg[belong[edge[i].to]]++; 100 } 101 Topo_dijs(S); 102 for(int i=1;i<=T;++i){ //统计答案 103 if(dis[i]==0x3f3f3f3f) 104 printf("NO PATH "); 105 else printf("%d ",dis[i]); 106 } 107 return 0; 108 }