zoukankan      html  css  js  c++  java
  • Luogu P5304 [GXOI/GZOI2019]旅行者|最短路

    链接

    题目大意:有一个(n)个点,(m)单向边的图,有(k)个特殊点,求(k)个特殊点间的最短路的最小值。

    (nle 10^5,mle 5 imes 10^5)

    题目思路:

    这个数据大小,用(k)次单源最短路的方法是不可行的。这里需要一个船新的方法:二进制分组

    方法是:每次将编号中某位二进制位不同点的分为两组,一组连源点,一组连汇点,边权均为(0),以源点为起点跑一次最短路,得出源点到汇点的最短路,再反着做一次(因为对于某个点对((x,y))((y,x))。它们的最短路可能不同)。每一位均操作一次后,得到的结果中的最小值即为答案。这样,我们只需要做约(log_2 k)次最短路,可以通过本题。

    我们来证明一下解法的正确性:
    对于所有的点对,每两个点必然有至少一位二进制位不同。也就是说,至少有一次分组中,两个点会被分到不同组。
    因此,上文解法相当于每个点对都做了次最短路,更准确的说,是一堆点对同时做一次最短路。

    上代码

    #include<bits/stdc++.h>
    #define S n+1
    #define T n+2
    #define N 100100
    #define M 700100
    using namespace std;
    int cc,to[M],net[M],fr[N],l[M],f[N],h[M],ha[M],p[N];bool vis[N];
    int g,n,m,k,t,u,v,len;
    void addedge(int u,int v,int len)
    {
    	cc++;
    	if (u==v) return ;
    	to[cc]=v;net[cc]=fr[u];fr[u]=cc;l[cc]=len;
    }
    void add(int x,int xx)
    {
    	g++;
    	h[g]=x;ha[g]=xx;
    	int fa=g/2,so=g;
    	while (h[fa]>h[so]&&fa)
    	{
    		swap(h[fa],h[so]);
    		swap(ha[fa],ha[so]);
    		so=fa;fa=fa/2;
    	}
    }
    int del()
    {
    	int ans=ha[1];
    	h[1]=h[g];ha[1]=ha[g];g--;
    	int fa=1,so=2;
    	if (h[so]>h[so+1]&&so+1<=g) so++;
    	while (h[fa]>h[so]&&so<=g)
    	{
    		swap(h[fa],h[so]);
    		swap(ha[fa],ha[so]);
    		fa=so;so*=2;
    		if (h[so]>h[so+1]&&so+1<=g) so++;
    	}
    	return ans; 
    } 
    void dij()
    {
    	for (int i=1;i<=n+2;i++) f[i]=2147483647,vis[i]=false;
    	f[S]=0;add(f[S],S);
    	while (g)
    	{
    		int x=del();
    		if (vis[x]) continue;
    		for (int i=fr[x];i;i=net[i])
    		{
    			if (f[to[i]]>f[x]+l[i])
    			{
    				f[to[i]]=f[x]+l[i];
    				add(f[to[i]],to[i]);
    			}
    		}
    	}
    }
    int main()
    {
    	cin>>t;
    	for (int tt=1;tt<=t;tt++)
    	{
    		cin>>n>>m>>k;
    		cc=0;int ans=2147483647;
    		for (int i=1;i<=n;i++) fr[i]=0;
    		for (int i=1;i<=m;i++)
    		{
    			cin>>u>>v>>len;
    			addedge(u,v,len);
    		}
    		for (int i=1;i<=k;i++)
    		  cin>>p[i];
    		for (int i=1;i<=k;i=i<<1)
    		{
    			cc=m;
    			for (int j=1;j<=k;j++)
    			{
    				if (i&j) addedge(S,p[j],0);else addedge(p[j],T,0);//二进制分组
    			}
    			dij();
    			ans=min(ans,f[T]);
    			for (int j=1;j<=k;j++)//复原
    			{
    				if (fr[p[j]]>m) fr[p[j]]=net[fr[p[j]]];
    			}
    			fr[S]=0;
    			cc=m;
    			for (int j=1;j<=k;j++)//反着做一次
    			{
    				if (!(i&j)) addedge(S,p[j],0);else addedge(p[j],T,0);
    			}
    			dij();
    			ans=min(ans,f[T]);
    			for (int j=1;j<=k;j++)
    			{
    				if (fr[p[j]]>m) fr[p[j]]=net[fr[p[j]]];
    			}
    			fr[S]=0;
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

    当然,还有另外的解法。该解法是更优的。似乎锤了std?

    考虑边((u,v)),经过这条边的,特殊点的最短路的最小值是(u)最近特殊点的最短路+((u,v))长度+离(v)最近的特殊点的最短路

    那么,正反做两遍dij(从源点开始)即可。

    没写代码

  • 相关阅读:
    UVA-10917 Walk Through the Forest (dijkstra+DP)
    UVA-11374 Airport Express (dijkstra+枚举)
    UVA-11294 Wedding (2-SAT)
    UVALive-3713 Astronauts (2-SAT)
    UVALive-3211 Now or later (2-SAT+二分)
    线程变量
    linux通用双向链表
    排序算法代码
    双向链表
    long与int的区别?(zz)
  • 原文地址:https://www.cnblogs.com/fmj123/p/14037022.html
Copyright © 2011-2022 走看看