洛谷:传送门

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 }
View Code

相关文章: