洛谷:传送门
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 }