洛谷 P5304 [GXOI/GZOI2019]旅行者(最短路)
洛谷:传送门
bzoj:传送门
参考资料:
[1]:https://xht37.blog.luogu.org/p5304-gxoigzoi2019-lv-xing-zhe
[2]:http://www.cnblogs.com/cjyyb/p/10736124.html
题意:
一个图 n 个点 m 条边,里面有 k 个特殊点,问这 k 个点之间两两最短路的最小值是多少?
之所以做这道题,是因为早晨的时候,做CF的这道题(戳这里),题意都木有读懂(😭),然后,搜了一篇博客(戳这里);
博主提到了这道题和 "GXOI2019旅行者" 基本类似,又说了一个之前从未见到过的名词 "二进制分组",so,就补了补这道题;
博文[1]思路(额外增加了些我的解释):
定义集合 K 中的元素为特殊的 k 个点;
假设我们把特殊点分成 A,B 两个集合:A={a1,a2,....,ax},B={b1,b2,....,by};
保证 ∀ai ∈ A , bi ∈ B,ai ∈ K,bi ∈ K ,且 A∩B = null,x+y = k;
新建节点 s 连向 A 集合的所有点,新增加的边权为0;
新建节点 t ,B 集合里的所有点连向 节点 t,新增加的边权为0 ;
那么 s 到 t 的最短路就是 A,B 集合点之间的最短路的最小值。
那么对于 k 个特殊点,我们枚举二进制里的第 i 位,把二进制第 i 位为 0 的点放在集合 A 中,为 的点放在集合 B中 ,用以上方法跑一个最短路。
然后跑 log n 次最短路之后,所有最短路的最小值就是最终答案。
原理是,假设 k 个特殊点里最近的是 x 和 y ,那么 x 和 y 一定有一个二进制位不一样;
那么他们肯定在那次分组的时候被放进了不同的集合,从而肯定被算进了最后的答案之中最短路。
AC代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 using namespace std; 6 #define ll long long 7 #define INFll 0x3f3f3f3f3f3f3f3f 8 #define mem(a,b) memset(a,b,sizeof(a)) 9 const int maxn=1e5+50; 10 11 int n,m,k; 12 int tourist[maxn]; 13 int num; 14 int head[maxn]; 15 int curNum; 16 int curHead[maxn]; 17 struct Edge 18 { 19 int to; 20 ll w; 21 int next; 22 }G[maxn*7]; 23 void addEdge(int u,int v,ll w) 24 { 25 G[num]={v,w,head[u]}; 26 head[u]=num++; 27 } 28 struct Dij 29 { 30 struct Heap 31 { 32 int u; 33 ll w; 34 bool operator < (const Heap& obj) const 35 { 36 return w > obj.w; 37 } 38 }; 39 priority_queue<Heap >q; 40 ll dist[maxn]; 41 bool vis[maxn]; 42 void dij(int s) 43 { 44 while(!q.empty()) 45 q.pop(); 46 mem(vis,false); 47 for(int i=0;i < maxn;++i) 48 dist[i]=INFll; 49 50 dist[s]=0; 51 q.push({s,0}); 52 while(!q.empty()) 53 { 54 Heap tmp=q.top(); 55 int u=tmp.u; 56 ll w=tmp.w; 57 q.pop(); 58 if(vis[u]) 59 continue; 60 vis[u]=true; 61 for(int i=head[u];~i;i=G[i].next) 62 { 63 int v=G[i].to; 64 ll w=G[i].w; 65 if(!vis[v] && dist[v] > dist[u]+w) 66 { 67 dist[v]=dist[u]+w; 68 q.push({v,dist[v]}); 69 } 70 } 71 } 72 } 73 }_dij; 74 75 void Debug() 76 { 77 for(int i=1;i <= n+2;++i) 78 { 79 printf("%d:",i); 80 for(int j=head[i];~j;j=G[j].next) 81 printf("->%d",G[j].to); 82 printf("\n"); 83 } 84 } 85 /** 86 后来的加边操作会改变某些节点i的head[i] 87 在执行下一次加边指令前要将其修改回来 88 不然,会出错 89 */ 90 void initHead()//初始化head[i],num 91 { 92 for(int i=1;i <= n;++i) 93 head[i]=curHead[i]; 94 head[n+1]=-1; 95 head[n+2]=-1; 96 num=curNum; 97 } 98 ll Solve() 99 { 100 ll ans=INFll; 101 int s=n+1; 102 int t=n+2; 103 for(int i=0;(1<<i) <= n;++i) 104 { 105 initHead(); 106 for(int j=1;j <= k;++j) 107 if(tourist[j]&(1<<i))//第i为为1的放入集合A 108 addEdge(s,tourist[j],0); 109 else//第i为为0的放入集合B 110 addEdge(tourist[j],t,0); 111 _dij.dij(s); 112 ans=min(ans,_dij.dist[t]); 113 114 initHead(); 115 for(int j=1;j <= k;++j) 116 if(tourist[j]&(1<<i))//第i为为1的放入集合B 117 addEdge(tourist[j],t,0); 118 else//第i为为0的放入集合A 119 addEdge(s,tourist[j],0); 120 _dij.dij(s); 121 ans=min(ans,_dij.dist[t]); 122 } 123 return ans; 124 } 125 void Init() 126 { 127 num=0; 128 mem(head,-1); 129 } 130 int main() 131 { 132 // freopen("C:\\Users\\hyacinthLJP\\Desktop\\in&&out\\contest","r",stdin); 133 int test; 134 while(~scanf("%d",&test)) 135 { 136 while(test--) 137 { 138 Init(); 139 scanf("%d%d%d",&n,&m,&k); 140 for(int i=1;i <= m;++i) 141 { 142 int x,y,z; 143 scanf("%d%d%d",&x,&y,&z); 144 addEdge(x,y,z); 145 } 146 curNum=num;//记录初始的num 147 for(int i=1;i <= n;++i) 148 curHead[i]=head[i];//记录节点i初始指向的head[i] 149 150 for(int i=1;i <= k;++i) 151 scanf("%d",tourist+i); 152 printf("%lld\n",Solve()); 153 } 154 } 155 return 0; 156 }
在洛谷上提交这种思路的代码,需要使用O2优化,不然,会有TLE的点;
在bzoj上提交是直接AC的;
博文[2]思路:
跑两遍Dij算法:
第一遍按照输入边的顺序,定义变量 dis[],pre[];
dis[u]:到达节点u的最短距离;
pre[u]:使得dis[u]最短的节点(路径记录,记的是集合 K 中的节点);
初始,dis[ K ] = 0,pre[ K ]=K;
对于 u->v 这条边,如果节点 u 更新了节点 v,那么 pre[v]=pre[u];
第二遍将输入的边反向,定义变量 disR[],preR[](含义同上);
如何更新答案呢?
对于 u->v 这条边,如果 preR[u] ≠ pre[v],那么 ans=min{ ans,disR[u] + wu->v + dis[v] };
都跑完后,输出 ans;
AC代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 using namespace std; 6 #define ll long long 7 #define INFll 0x3f3f3f3f3f3f3f3f 8 #define mem(a,b) memset(a,b,sizeof(a)) 9 const int maxn=1e5+50; 10 11 int n,m,k; 12 ll ans; 13 int tourist[maxn]; 14 int num; 15 int head[maxn]; 16 struct Edge 17 { 18 int to; 19 ll w; 20 int next; 21 }G[maxn*10]; 22 void addEdge(int u,int v,ll w) 23 { 24 G[num]={v,w,head[u]}; 25 head[u]=num++; 26 } 27 struct Dij 28 { 29 struct Heap 30 { 31 int u; 32 ll w; 33 bool operator < (const Heap& obj) const 34 { 35 return w > obj.w; 36 } 37 }; 38 39 ll dis[maxn]; 40 int pre[maxn]; 41 bool vis[maxn]; 42 priority_queue<Heap >q; 43 void dij()//第一遍dij求解出pre[],dis[] 44 { 45 mem(vis,false); 46 while(!q.empty()) 47 q.pop(); 48 for(int i=0;i < maxn;++i) 49 dis[i]=INFll; 50 for(int i=1;i <= k;++i) 51 { 52 int u=tourist[i]; 53 dis[u]=0; 54 pre[u]=u; 55 q.push({u,0}); 56 } 57 while(!q.empty()) 58 { 59 Heap tmp=q.top(); 60 q.pop(); 61 int u=tmp.u; 62 if(vis[u]) 63 continue; 64 vis[u]=true; 65 for(int i=head[u];~i;i=G[i].next) 66 { 67 int v=G[i].to; 68 ll w=G[i].w; 69 if(i&1) 70 continue; 71 if(dis[v] > dis[u]+w) 72 { 73 dis[v]=dis[u]+w; 74 pre[v]=pre[u]; 75 q.push({v,dis[v]}); 76 } 77 } 78 } 79 } 80 ll disR[maxn]; 81 int preR[maxn]; 82 void dijR()//第二遍dij求出disR[],preR[],ans 83 { 84 mem(vis,false); 85 while(!q.empty()) 86 q.pop(); 87 for(int i=0;i < maxn;++i) 88 disR[i]=INFll; 89 for(int i=1;i <= k;++i) 90 { 91 int u=tourist[i]; 92 disR[u]=0; 93 preR[u]=u; 94 q.push({u,0}); 95 } 96 while(!q.empty()) 97 { 98 Heap tmp=q.top(); 99 q.pop(); 100 int u=tmp.u; 101 if(vis[u]) 102 continue; 103 vis[u]=true; 104 /** 105 错误的更新ans判断; 106 假设最终答案是 x->y 的最短路; 107 如果只是判断节点u的preR和pre是否相等; 108 假设pre[u]=x; 109 那么,如果 w[x][u] = w[y][u]; 110 根据更新条件,此处 w 相等,不更新; 111 所以 preR[u]=w; 112 来到当前if的时候,preR[u]=pre[u]=x; 113 但是令 preR[u]=y 也可以; 114 所以,在这种情况下,本来该更新ans的并没有更新 115 */ 116 // if(preR[u] != pre[u]) 117 // ans=min(ans,dis[u]+disR[u]); 118 for(int i=head[u];~i;i=G[i].next) 119 { 120 int v=G[i].to; 121 ll w=G[i].w; 122 if(!(i&1)) 123 continue; 124 if(preR[u] != pre[v])//如果更新u,v的不是K中的同一个点,更新ans 125 ans=min(ans,disR[u]+w+dis[v]); 126 if(disR[v] > disR[u]+w) 127 { 128 disR[v]=disR[u]+w; 129 preR[v]=preR[u]; 130 q.push({v,disR[v]}); 131 } 132 } 133 } 134 } 135 }_dij; 136 ll Solve() 137 { 138 ans=INFll; 139 _dij.dij(); 140 _dij.dijR(); 141 return ans; 142 } 143 void Init() 144 { 145 num=0; 146 mem(head,-1); 147 } 148 int main() 149 { 150 // freopen("C:\\Users\\hyacinthLJP\\Desktop\\in&&out\\contest","r",stdin); 151 int test; 152 while(~scanf("%d",&test)) 153 { 154 while(test--) 155 { 156 Init(); 157 scanf("%d%d%d",&n,&m,&k); 158 for(int i=1;i <= m;++i) 159 { 160 int u,v,w; 161 scanf("%d%d%d",&u,&v,&w); 162 addEdge(u,v,w);//num为偶数,正向边 163 addEdge(v,u,w);//num为奇数,反向边 164 } 165 for(int i=1;i <= k;++i) 166 scanf("%d",tourist+i); 167 printf("%lld\n",Solve()); 168 } 169 } 170 return 0; 171 }