洛谷P3008 [USACO11JAN]Roads and Planes G

 

 

思路分析:

解法一:

  由于这道题提到可能有负权边,那么dijsktra算法肯定首先就被我们排除了,floyd?想都不要想,最后我们只剩下了Spfa,但众所周知,USACO是卡Spfa的,但似乎没有其他的解法了,于是我们就可以像一道最短路板子题一样建图,从起点出发,跑一遍Spfa,交上去,果不其然,TLE掉了两个点。但也只有两个点,于是我们想到用SLF去优化它,所谓SLF,就是在Spfa入队时拿将要入队的数和队首元素比较,如果比队首元素小则插入队首,否则插在対尾,于是用到双端队列,每次入队前判断一下就好了。这样我们就可以在开O2的前提下水过这道题了,但不开O2还是会TLE掉一个点,其实这道题只是不想卡SLF,但SLF优化并没有基于复杂度,实际上可以构造数据来卡到2^n,甚至可能比普通spfa更慢(以上一句话来自洛谷大佬(id:1010_)),但既然能过,也是一种方法。

上代码:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<queue>
 5 using namespace std;
 6 #define debug printf("------------\n");
 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\n");
57         else printf("%d\n",dis[i]);
58     }
59     return 0;
60 }
View Code

解法二:

  虽然这道题明确说了会有负权,但那只是单向航道,双向的道路的权值仍然都是正值,所以在双向的道路内是符合dijsktra的使用前提的,于是我们想到缩点,我们可以把所有权值为正且相邻的点块缩在一起,这样就形成了一个DAG之后用拓扑排序在DAG上求解即可。需要注意的是,需要将不能互通的两点间距离初始化为一个很大的数,并在拓扑时要将所有入度为0的点算进去,防止出现不互达的情况。有了思路代码就不难写了,这样写能比解法一的时间快4倍左右。

向量是真的难用调的我都要吐了

上代码:

  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\n",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\n");
105         else printf("%d\n",dis[i]);
106     }
107     return 0;
108 }
View Code

猜你喜欢

转载自www.cnblogs.com/li-jia-hao/p/12808136.html