题目大意:给一个无向图,边权均为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]);
}