1. 程式人生 > 實用技巧 >CF1051F The Shortest Statement

CF1051F The Shortest Statement

題意簡述

題目連結

  給定一張n個點m條邊的無向圖,滿足m-n<=20,然後進行q次操作,每次給定兩個點,詢問兩點間最短路。

  資料範圍:1<=n,m,q<=105

演算法概述

  只看題面顯然是個裸的全源最短路,但是再看資料範圍……顯然不是全源最短路。

  所以這時候就需要發揮我們的聰明才智,在題目中找一些特殊性質或者發現一些結論之類的了。

  顯然,這道題最特殊的地方在於m-n<=20的限制,相當於在一棵樹上多出了最多21條非樹邊。

  對於給定的任意兩個點a,b,最短路無非兩種情況:

  1.只經過樹邊,樹上距離簡單容斥一下,d[a]+d[b]-2*d[lca(a,b)];

  2.經過非樹邊,那麼我們可以列舉所有非樹邊,對於每條非樹邊(u,v,w),其構成的最短路有且僅有兩種情況,其一是a→u→v→b,其二則是a→v→u→b,兩者取min即可。

  那麼我們的演算法框架就出來了:

  先求一棵生成樹(以下用Kruskal),然後把非樹邊處理出來。

  先在樹上做預處理,求出每個點到根的距離d。

  把非樹邊加進去,然後對於所有在非樹邊上的點跑一遍最短路。

  最後處理詢問。

  時間複雜度O(21*q+42*mlogn)

參考程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const int N=1e5+10;
struct Edge{
	int to,next,w;
}edge[N<<1];int idx;
int h[N];

void add_edge(int u,int v,int w){edge[++idx]={v,h[u],w};h[u]=idx;}

struct E{
	int u,v,w;
	bool operator <(const E &ed)const{
		return w<ed.w;
	}
}e[N],spc[N];int cnt;

int fa[N];
ll dis[50][N];
int vis[N];
int n,m,q;

int dep[N],son[N],siz[N];
int f[N],top[N];
ll d[N];

void dfs1(int p,int father)
{
	f[p]=father;
	dep[p]=dep[father]+1;
	siz[p]=1;
	int max_son=0;
	for(int i=h[p];~i;i=edge[i].next)
	{
		int to=edge[i].to,w=edge[i].w;
		if(to==father)continue;
		d[to]=d[p]+w;
		dfs1(to,p);
		if(siz[to]>max_son)max_son=siz[to],son[p]=to;
		siz[p]+=siz[to];
	}
}

void dfs2(int p,int t)
{
	top[p]=t;
	if(!son[p])return;
	dfs2(son[p],t);
	for(int i=h[p];~i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(to==f[p]||to==son[p])continue;
		dfs2(to,to);
	}
}

inline int lca(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=f[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}

int get(int x)
{
	if(fa[x]==x)return fa[x];
	return fa[x]=get(fa[x]);
}

void dijkstra(int s,ll dist[])
{
	memset(vis,0,sizeof vis);
	priority_queue<pair<ll,int> > q;
	q.push(make_pair(0,s));
	dist[s]=0;
	while(!q.empty())
	{
		int p=q.top().second;
		q.pop();
		if(vis[p])continue;
		vis[p]=1;
		for(int i=h[p];~i;i=edge[i].next)
		{
			int to=edge[i].to,w=edge[i].w;
			if(dist[to]>dist[p]+w)
			{
				dist[to]=dist[p]+w;
				q.push(make_pair(-dist[to],to));
			}
		}
	}
}

int main()
{
	memset(h,-1,sizeof h);
	memset(dis,0x3f,sizeof dis);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++)scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
	sort(e+1,e+m+1);
	for(int i=1;i<=m;i++)
	{
		int u=e[i].u,v=e[i].v,w=e[i].w;
		u=get(u),v=get(v);
		if(u==v)
		{
			spc[++cnt]=e[i];
			continue;
		}
		fa[u]=v;
		add_edge(e[i].u,e[i].v,w);
		add_edge(e[i].v,e[i].u,w);
	}
	
	dfs1(1,0);
	dfs2(1,1);
	
	for(int i=1;i<=cnt;i++)
	{
		int u=spc[i].u,v=spc[i].v,w=spc[i].w;
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	
	for(int i=1;i<=cnt;i++)
	{
		int u=spc[i].u,v=spc[i].v;
		dijkstra(u,dis[(i<<1)-1]);
		dijkstra(v,dis[i<<1]);
	}
	scanf("%d",&q);
	while(q--)
	{
		int a,b;scanf("%d%d",&a,&b);
		ll ans=d[a]+d[b]-2*d[lca(a,b)];
		for(int i=1;i<=cnt;i++)
		{
			int u=(i<<1)-1,v=i<<1,w=spc[i].w;
			ans=min(ans,dis[u][a]+dis[v][b]+w);
			ans=min(ans,dis[u][b]+dis[v][a]+w);
		}
		printf("%lld\n",ans);
	}
	return 0;
}