题意:模拟了汽车的行驶过程,边上的权值为全速通过所消耗的时间,而起步(从起点出发的边)、刹车(到终点结束的边)、减速(即将拐弯的边)、加速(刚完成拐弯的边)这四种不能达到全速的情况,消耗的时间为权值*2。问从起点到终点所消耗的最少时间。
这道题主要是建图,很复杂,无耻地照着书上的代码码了一遍。让状态搞糊涂了= =
注意:
1、grid[][][4]记录了点的上下左右四条边的权值,id[][][4][2]记录各个点。
2、到一个点的最短路可以是路过这个点再折返回来,e.g:1->2 c=17,2->3 c=4,从1->2的最短路为17+8+8=33<34
3、原图总点数上限100*100,而拆点后共有8*100*100个点,狠狠地RE了一发。
1 #include<cstdio> 2 #include<cstring> 3 #include<vector> 4 #include<queue> 5 #include<algorithm> 6 using namespace std; 7 8 const int MAXN=111111; 9 const int maxr=111,maxc=111; 10 const int INF =1e9; 11 12 const int UP=0,LEFT=1,DOWN=2,RIGHT=3; 13 const int inv[]={2,3,0,1}; 14 const int dr[]={-1,0,1,0}; 15 const int dc[]={0,-1,0,1}; 16 17 struct Edge{ 18 int u,v,c; 19 }; 20 21 struct HeapNode{ 22 int c,u; 23 bool operator < (const HeapNode rhs)const { 24 return c>rhs.c; 25 } 26 }; 27 28 struct Dijkstra{ 29 int n,m; 30 vector<Edge>edges; 31 vector<int>G[MAXN]; 32 bool vis[MAXN]; 33 int d[MAXN],p[MAXN]; 34 35 void init(int n) 36 { 37 this->n=n; 38 for(int i=0;i<n;i++) 39 G[i].clear(); 40 edges.clear(); 41 } 42 43 void add(int u,int v,int c) 44 { 45 edges.push_back((Edge){u,v,c}); 46 m=edges.size(); 47 G[u].push_back(m-1); 48 } 49 50 void dijkstra(int st) 51 { 52 priority_queue<HeapNode>q; 53 for(int i=0;i<n;i++) 54 d[i]=INF; 55 d[st]=0; 56 memset(vis,0,sizeof(vis)); 57 q.push((HeapNode){0,st}); 58 while(!q.empty()) 59 { 60 HeapNode x=q.top();q.pop(); 61 int u=x.u; 62 if(vis[u]) 63 continue; 64 vis[u]=true; 65 int sz=G[u].size(); 66 for(int i=0;i<sz;i++) 67 { 68 Edge e=edges[G[u][i]]; 69 if(d[e.v]>d[u]+e.c){ 70 d[e.v]=d[u]+e.c; 71 q.push((HeapNode){d[e.v],e.v}); 72 } 73 } 74 } 75 } 76 }; 77 78 int grid[maxr][maxc][4]; 79 80 int n,id[maxr][maxc][4][2]; 81 82 int ID(int r,int c,int dir,int doubled) 83 { 84 int& x=id[r][c][dir][doubled]; 85 if(x==0)x=++n; 86 return x; 87 } 88 89 int R,C; 90 91 bool cango(int r,int c,int dir) 92 { 93 if(r<0||r>=R||c<0||c>=C) 94 return false; 95 return grid[r][c][dir]>0; 96 } 97 98 Dijkstra solver; 99 100 int readint() 101 { 102 int x; 103 scanf("%d",&x); 104 return x; 105 } 106 107 int main() 108 { 109 int r1,c1,r2,c2,kase=0; 110 while(scanf("%d%d%d%d%d%d",&R,&C,&r1,&c1,&r2,&c2)==6&&R) 111 { 112 r1--;c1--;r2--;c2--; 113 memset(grid,0,sizeof(grid)); 114 for(int r=0;r<R;r++)//每次更新点(r,c)的下方和右方的边 115 { 116 for(int c=0;c<C-1;c++) 117 grid[r][c][RIGHT]=grid[r][c+1][LEFT]=readint(); 118 if(r!=R-1) 119 for(int c=0;c<C;c++) 120 grid[r][c][DOWN]=grid[r+1][c][UP]=readint(); 121 } 122 123 solver.init(R*C*8+1); 124 125 n=0; 126 memset(id,0,sizeof(id)); 127 128 for(int dir=0;dir<4;dir++)//对起点做double 129 if(cango(r1,c1,dir)) 130 solver.add(0,ID(r1+dr[dir],c1+dc[dir],dir,1),grid[r1][c1][dir]*2); 131 132 for(int r=0;r<R;r++)//要把所有点都处理完整,不能到r2,c2就结束 133 for(int c=0;c<C;c++) 134 for(int dir=0;dir<4;dir++) 135 if(cango(r,c,inv[dir])) 136 //为什么是inv?grid[r][c][inv[dir]]是点(r,c)沿inv[dir]方向到点x的边权,同样也是点x->点(r,c)的边权:老边 137 //而区别在于判断进入(r,c)的边,与出(r,c)进(new,r,newc)的边方向是否一致 138 for(int newdir=0;newdir<4;newdir++) 139 if(cango(r,c,newdir)) 140 for(int doubled=0;doubled<2;doubled++){ 141 int newr=r+dr[newdir]; 142 int newc=c+dc[newdir]; 143 int v=grid[r][c][newdir],newdoubled=0; 144 if(dir!=newdir){//若方向不一致,两条边都要加倍 145 if(!doubled)//对于前一条边分两种情况讨论:已经double(之前已经加速或起步),未double 146 v+=grid[r][c][inv[dir]];// 147 newdoubled=1; 148 v+=grid[r][c][newdir]; 149 } 150 solver.add(ID(r,c,dir,doubled),ID(newr,newc,newdir,newdoubled),v); 151 //若方向一致,连到newdouble==0的两条边权值相同 152 //否则,连到newdouble==1的两条边相差grid[r][c][inv[dir]],即分开讨论老边是否已加倍,由于最后求最短路,不影响 153 } 154 solver.dijkstra(0); 155 156 int ans=INF; 157 for(int dir=0;dir<4;dir++)//从与终点(r2,c2)相连的八个状态中取最小值 158 if(cango(r2,c2,inv[dir])) 159 for(int doubled=0;doubled<2;doubled++) 160 { 161 int v=solver.d[ID(r2,c2,dir,doubled)]; 162 if(!doubled) 163 v+=grid[r2][c2][inv[dir]]; 164 ans=min(ans,v); 165 } 166 167 printf("Case %d: ",++kase); 168 if(ans==INF) 169 printf("Impossible "); 170 else 171 printf("%d ",ans); 172 } 173 return 0; 174 }
后记:
这里的数组id[][][][],以及函数 ID()的处理很值得学习一下。在处理多状态等复杂情况是很实用。
用这个方法自己写了一道题,一遍就通过了样例,好有成就感,可惜uva在这道题上又挂了= =