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

在洛谷上提交这种思路的代码,需要使用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 }
View Code

 

 

 

posted @ 2019-04-24 13:53  HHHyacinth  阅读(462)  评论(0编辑  收藏  举报