BZOJ3415: Poi2013 Price List

本文介绍了一种针对特定条件下的最短路径问题的优化算法,该算法通过BFS结合边删除策略来降低时间复杂度。文章详细解释了不同场景下的最短路径计算方法,并提出了一种高效遍历策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:给一个无向图,边权均为a,然后将原来图中满足最短路等于2a所有的点对(x,y)之间再加一条长度为b的无向边,问操作之后点K到所有点的最短路是多少

首先我们考虑最短路的几种情况:1.就是按照边权全是a那么走
2.(当b<2a时)可以将原来最短路中每两条边合成一个b走下来
3.(当b<a时)这时有一种奇特的走法,比如当原来最短路长度是奇数时沿着原来的走就必然要走一个a,假如a很大就不划算了,所以可以在原图上找到一种边数较多的方法,但是恰好相邻两点间都是b,这样路程更短

前两种可以直接BFS出来,关键是第三种
首先可以想到一个比较暴力的方法,还是BFS,然后对于每个点,先遍历他的所有相邻结点,再遍历这些点的相邻结点,然后把它们推进队列,这样时间复杂度可以达到O(M^2)
然后我们可以想到一个小优化
当我们取出队头一个元素后,先遍历和他相邻的节点,我们称之为一次遍历,然后再遍历第二波相邻结点,我们称之为二次遍历
我们可以发现,当一次遍历扩展到一个节点x,并由他再二次遍历扩展到一个新的节点y时,边(x,y)再二次遍历中就再也没有用了,因为y这个点已经进入了队列而x没有,也就是说,x还有机会成为一次遍历产生的点,这时再由他二次遍历时,原来已经进入队列的就不需要在进入队列了,所以这条边没用了可以删掉,删掉可以用DCEL(双向边表)
然而这不仅仅是个小优化

POI官网上给出了时间复杂度证明:
首先,时间复杂度约等于遍历的边的数量
所以我们只需要考虑那些遍历了却没被删掉的边的数量
对于每一个节点x,由他开始进行一次遍历再二次遍历中,没被删掉的边只有一种,就是在二次遍历中遍历到了一个与x距离为1的点,也就是一个三元环
所以对于这个节点x,假设和他距离为1的点有k个,娜美这次最多有O(k^2)条边遍历过但没有被删掉
又因为总边数一共只有m条,所以总时间复杂度为
出题人怎么想到的......

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200010
#define ZZ(i,x) for(i=Z.pre[x];i;i=Z.nxt[i])
#define FF(i,x) for(i=F.pre[x];i;i=F.nxt[i])
using namespace std;
struct ljb
{
	int to[N],nxt[N],pre[N],bef[N],cnt;
	void ae(int ff,int tt)
	{
		cnt++;
		to[cnt]=tt;
		nxt[cnt]=pre[ff];
		bef[pre[ff]]=cnt;
		pre[ff]=cnt;
	}
	void del(int x,int i)
	{
		if(i==pre[x]) pre[x]=nxt[i];
		nxt[bef[i]]=nxt[i];
		bef[nxt[i]]=bef[i];
	}
}Z,F;
int q[N],d[N],ans[N];
bool vis[N];
int main()
{
	int n,m,s,a,b;
	scanf("%d%d%d%d%d",&n,&m,&s,&a,&b);
	int i,j,k,l,x,y;
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		Z.ae(x,y);Z.ae(y,x);
		F.ae(x,y);F.ae(y,x);
	}
	int h=1,t=1;
	q[1]=s;
	while(h<=t)
	{
		x=q[h];h++;
		ZZ(i,x)
		{
			j=Z.to[i];
			if(!d[j]&&j!=s)
			{
				d[j]=d[x]+1;
				t++;q[t]=j;
			}
		}
	}
	for(i=1;i<=n;i++)
	ans[i]=min(d[i]*a,(d[i]/2)*b+(d[i]%2)*a);
	memset(d,0,sizeof(d));
	h=1;t=1;
	q[1]=s;
	while(h<=t)
	{
		x=q[h];h++;
		ZZ(i,x)
		{
			j=Z.to[i];
			vis[j]=true;
		}
		ZZ(i,x)
		{
			j=Z.to[i];
			FF(k,j)
			{
				l=F.to[k];
				if(d[l]||l==s||vis[l]) continue;
				d[l]=d[x]+1;
				t++;q[t]=l;
				F.del(j,k);
			}
		}
		ZZ(i,x)
		{
			j=Z.to[i];
			vis[j]=false;
		}
	}
	for(i=1;i<=n;i++)
	if(d[i])
	ans[i]=min(ans[i],d[i]*b);
	for(i=1;i<=n;i++)
	printf("%d\n",ans[i]);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值