#3087. 「GXOI / GZOI2019」旅行者
考察:最短路+思维
完全不会,废物本废
错误思路:
比较明显的思路就是以每个特殊点为起点,然后Dijkstra,再以每个特殊点为终点求最小值,显而易见地TLE
解法一(官方题解):
由暴力思路延伸来的思路.因为我们只需要知道最小值,而不是特殊点.所以起点和终点是谁无所谓.所以可以将上述的一个个求最小值优化为一组组求最小值.
比较难想的如何分组(对本蒟蒻而言),这题的分组思路是枚举二进制的第i位,如果第i位是1,就作为起点,如果第i位是0,就作为终点.但是这道题求完Dijkstra后还需要一个个枚举终点距离,更好的做法是建立虚点S,E.S与起点建立0的边,E与终点建立0的边.
n最多有17位,所以进行17次dijkstra.但是注意是需要起点终点互换求两次,否则无法覆盖全部方案.
时间复杂度:O(T*17*2*m*log2n)
1 #include <iostream> 2 #include <cstring> 3 #include <queue> 4 #include <vector> 5 using namespace std; 6 typedef long long LL; 7 typedef pair<LL,int> PII; 8 const int N = 100110,M = 500010; 9 int n,m,k,idx,h[N],ask[N],backup[N],S,E; 10 LL dist[N],res = (1ll<<63)-1; 11 bool st[N]; 12 struct Road{ 13 int to,ne,w; 14 }road[M+N]; 15 void dijkstra(int s,int e) 16 { 17 for(int i=1;i<=n+3;i++) dist[i] = 1e14; 18 memset(st,0,sizeof st); 19 dist[s] = 0; 20 priority_queue<PII,vector<PII>,greater<PII> > q; 21 q.push({0,s}); 22 while(q.size()) 23 { 24 PII it = q.top(); 25 int u = it.second; 26 q.pop(); 27 if(st[u]) continue; 28 st[u] = 1; 29 for(int i=h[u];~i;i=road[i].ne) 30 { 31 int v = road[i].to; 32 if(dist[v]>dist[u]+road[i].w) 33 { 34 dist[v] = dist[u]+road[i].w; 35 q.push({dist[v],v}); 36 } 37 } 38 } 39 res = min(res,dist[e]); 40 } 41 void add(int a,int b,int w) 42 { 43 road[idx].to = b,road[idx].w = w,road[idx].ne = h[a],h[a] = idx++; 44 } 45 int main() 46 { 47 int T; 48 scanf("%d",&T); 49 while(T--) 50 { 51 memset(h,-1,sizeof h); idx = 0; 52 res = (1ll<<63)-1; 53 scanf("%d%d%d",&n,&m,&k); 54 while(m--) 55 { 56 int a,b,c; scanf("%d%d%d",&a,&b,&c); 57 if(a==b) continue; 58 add(a,b,c); 59 } 60 for(int i=1;i<=k;i++) scanf("%d",&ask[i]); 61 S = n+1,E = n+2; 62 int t = idx; 63 for(int i=1;i<=n+3;i++) backup[i] = h[i]; 64 for(int i=0;i<=17;i++) 65 { 66 idx = t,memcpy(h,backup,sizeof h); 67 for(int j=1;j<=k;j++) 68 {//1作起点 69 if(ask[j]>>i&1) add(S,ask[j],0); 70 else add(ask[j],E,0);//0作终点 71 } 72 dijkstra(S,E); 73 int t = idx; 74 for(int i=1;i<=n+3;i++) h[i] = backup[i]; 75 for(int j=1;j<=k;j++) 76 {//1作终点 77 if((ask[j]>>i&1)) add(ask[j],E,0); 78 else add(S,ask[j],0);//0作终点 79 } 80 dijkstra(S,E); 81 } 82 printf("%lld\n",res); 83 } 84 return 0; 85 }
解法二(神仙思路):
易知答案一定经过了某条边.所以枚举边{u,v,w} 先求所有特殊点到u的最短距离,再求v到所有特殊点的最短距离. ans = dist[u]+此路权值+dist[v]
因为涉及v到所有特殊点的最短距离,所以需要建立反向边.但这里有个特殊情况.就是如果u,v最近的特殊点是同一个答案就不合法.所有需要在Dijkstra时求出距离每个点最近的特殊点.
1 #include <iostream> 2 #include <cstring> 3 #include <queue> 4 #include <vector> 5 using namespace std; 6 typedef long long LL; 7 typedef pair<LL,int> PII; 8 const int N = 100110,M = 500010; 9 int n,m,k,idx,h[N],ask[N],a[M],b[M],w[M]; 10 int color[2][N]; 11 bool st[N]; 12 LL dist[2][N]; 13 struct Road{ 14 int to,ne,w; 15 }road[M+N]; 16 void add(int a,int b,int w) 17 { 18 road[idx].to = b,road[idx].w = w,road[idx].ne = h[a],h[a] = idx++; 19 } 20 void dijkstra(int p) 21 { 22 priority_queue<PII,vector<PII>,greater<PII> > q; 23 for(int i=1;i<=n;i++) dist[p][i] = 1e14,st[i] = 0,color[p][i] = 0; 24 for(int i=1;i<=k;i++) dist[p][ask[i]] = 0,color[p][ask[i]] = ask[i],q.push({0,ask[i]}); 25 while(q.size()) 26 { 27 PII it = q.top(); 28 q.pop(); 29 int u = it.second; 30 if(st[u]) continue; 31 st[u] = 1; 32 for(int i=h[u];~i;i=road[i].ne) 33 { 34 int v = road[i].to; 35 if(dist[p][v]>dist[p][u]+road[i].w) 36 { 37 dist[p][v] = dist[p][u]+road[i].w; 38 color[p][v] = color[p][u];//标记离v点最近的u 39 q.push({dist[p][v],v}) ; 40 } 41 } 42 } 43 } 44 int main() 45 { 46 int T; 47 scanf("%d",&T); 48 while(T--) 49 { 50 memset(h,-1,sizeof h); idx = 0; 51 scanf("%d%d%d",&n,&m,&k); 52 for(int i=1;i<=m;i++) scanf("%d%d%d",&a[i],&b[i],&w[i]); 53 for(int i=1;i<=k;i++) scanf("%d",&ask[i]); 54 //正向建边 55 for(int i=1;i<=m;i++) 56 if(a[i]!=b[i]) add(a[i],b[i],w[i]); 57 dijkstra(0);//作起点的时候 58 //反向建边 59 memset(h,-1,sizeof h); idx = 0; 60 for(int i=1;i<=m;i++) 61 if(a[i]!=b[i]) add(b[i],a[i],w[i]); 62 dijkstra(1); 63 LL ans = (1ll<<63)-1; 64 for(int i=1;i<=m;i++) 65 if(color[0][a[i]]&&color[1][b[i]]&&color[0][a[i]]!=color[1][b[i]]) 66 ans = min(dist[0][a[i]]+dist[1][b[i]]+w[i],ans); 67 printf("%lld\n",ans); 68 } 69 return 0; 70 }